| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218 |
- /**
- * 执行操作序列(schedule/if/for/while + 普通步骤)
- * 单文件 ≤500 行。ctx: executeAction, logMessage, evaluateCondition, getActionName, parseDelayString, calculateWaitTime, state
- * state: variableContext, globalStepCounter, currentWorkflowFolderPath, variableContextInitialized
- */
- async function executeActionSequence(
- actions,
- device,
- folderPath,
- resolution,
- stepInterval,
- onStepComplete,
- shouldStop,
- depth,
- ctx
- ) {
- const { executeAction, logMessage, evaluateCondition, getActionName, parseDelayString, calculateWaitTime, replaceVariablesInString, state } = ctx
- const variableContext = state.variableContext
- const DEFAULT_STEP_INTERVAL = ctx.DEFAULT_STEP_INTERVAL ?? 1000
- if (depth === 0) {
- state.globalStepCounter = 0
- state.variableContextInitialized = false
- state.currentWorkflowFolderPath = folderPath
- }
- let completedSteps = 0
- const interval = stepInterval ?? DEFAULT_STEP_INTERVAL
- for (let i = 0; i < actions.length; i++) {
- if (shouldStop && shouldStop()) return { success: false, error: 'Execution stopped', completedSteps }
- const action = actions[i]
- if (action.type === 'schedule') {
- const condition = action.condition || {}
- let intervalStr = condition.interval || '0s'
- if (replaceVariablesInString && typeof intervalStr === 'string') {
- intervalStr = replaceVariablesInString(intervalStr, variableContext)
- }
- let repeat = condition.repeat !== undefined ? condition.repeat : 1
- if (typeof repeat === 'string' && variableContext) {
- const varName = repeat.replace(/^\{|\}$/g, '').trim()
- const resolved = variableContext[varName]
- repeat = resolved !== undefined && resolved !== null && resolved !== '' ? (parseInt(resolved, 10) || 1) : 1
- }
- const actionsToExecute = action.interval || []
- const intervalMs = parseDelayString(intervalStr) || 0
- const maxIterations = repeat === -1 ? Infinity : (typeof repeat === 'number' ? repeat : 1)
- let iteration = 0
- while (iteration < maxIterations) {
- if (shouldStop && shouldStop()) return { success: false, error: 'Execution stopped', completedSteps }
- iteration++
- if (iteration > 1 && intervalMs > 0) {
- let remainingTime = intervalMs
- const countdownInterval = 100
- while (remainingTime > 0) {
- if (shouldStop && shouldStop()) return { success: false, error: 'Execution stopped', completedSteps }
- const waitTime = Math.min(countdownInterval, remainingTime)
- await new Promise(resolve => setTimeout(resolve, waitTime))
- remainingTime -= waitTime
- }
- }
- if (actionsToExecute.length > 0) {
- const result = await executeActionSequence(actionsToExecute, device, folderPath, resolution, interval, onStepComplete, shouldStop, depth + 1, ctx)
- if (!result.success) return result
- completedSteps += result.completedSteps || 0
- }
- }
- continue
- }
- if (action.type === 'if') {
- const conditionResult = evaluateCondition(action.condition, variableContext)
- const actionsToExecute = conditionResult ? (action.then || action.ture || []) : (action.else || action.false || [])
- if (actionsToExecute.length > 0) {
- const result = await executeActionSequence(actionsToExecute, device, folderPath, resolution, interval, onStepComplete, shouldStop, depth + 1, ctx)
- if (!result.success) return result
- completedSteps += result.completedSteps || 0
- }
- continue
- }
- if (action.type === 'for') {
- if (action.times != null) {
- let count = action.times
- if (typeof count === 'string') {
- const varName = count.replace(/^\{|\}$/g, '').trim()
- count = Math.max(0, parseInt(variableContext[varName], 10) || 0)
- } else {
- count = Math.max(0, parseInt(count, 10) || 0)
- }
- for (let i = 0; i < count; i++) {
- if (shouldStop && shouldStop()) return { success: false, error: 'Execution stopped', completedSteps }
- if (action.variable) variableContext[action.variable.replace(/^\{|\}$/g, '').trim()] = i
- if (action.body && action.body.length > 0) {
- const result = await executeActionSequence(action.body, device, folderPath, resolution, interval, onStepComplete, shouldStop, depth + 1, ctx)
- if (!result.success) return result
- completedSteps += result.completedSteps || 0
- }
- }
- } else {
- const items = Array.isArray(action.items) ? action.items : []
- for (const item of items) {
- if (shouldStop && shouldStop()) return { success: false, error: 'Execution stopped', completedSteps }
- if (action.variable) variableContext[action.variable.replace(/^\{|\}$/g, '').trim()] = item
- if (action.body && action.body.length > 0) {
- const result = await executeActionSequence(action.body, device, folderPath, resolution, interval, onStepComplete, shouldStop, depth + 1, ctx)
- if (!result.success) return result
- completedSteps += result.completedSteps || 0
- }
- }
- }
- continue
- }
- if (action.type === 'while') {
- while (evaluateCondition(action.condition, variableContext)) {
- if (shouldStop && shouldStop()) return { success: false, error: 'Execution stopped', completedSteps }
- if (action.body && action.body.length > 0) {
- const result = await executeActionSequence(action.body, device, folderPath, resolution, interval, onStepComplete, shouldStop, depth + 1, ctx)
- if (!result.success) return result
- completedSteps += result.completedSteps || 0
- }
- }
- continue
- }
- if (action.type === 'try') {
- const tryActions = action.try || action.body || []
- const successActions = action.success || []
- const failActions = action.fail || action.catch || []
- const result = tryActions.length > 0
- ? await executeActionSequence(tryActions, device, folderPath, resolution, interval, onStepComplete, shouldStop, depth + 1, ctx)
- : { success: true, completedSteps: 0 }
- if (result.success && successActions.length > 0) {
- const successResult = await executeActionSequence(successActions, device, folderPath, resolution, interval, onStepComplete, shouldStop, depth + 1, ctx)
- if (!successResult.success) return successResult
- completedSteps += (result.completedSteps || 0) + (successResult.completedSteps || 0)
- } else if (result.success) {
- completedSteps += result.completedSteps || 0
- } else {
- const errMsg = (result.error != null && result.error !== '') ? String(result.error) : 'Unknown error'
- const timeStr = new Date().toISOString().replace('T', ' ').slice(0, 19)
- await logMessage(`[sequence-runner] [try failed] ${timeStr} ${errMsg}`, folderPath).catch(() => {})
- if (failActions.length > 0) {
- const failResult = await executeActionSequence(failActions, device, folderPath, resolution, interval, onStepComplete, shouldStop, depth + 1, ctx)
- if (!failResult.success) return failResult
- completedSteps += (result.completedSteps || 0) + (failResult.completedSteps || 0)
- } else {
- return result
- }
- }
- continue
- }
- const times = action.times || 1
- if (onStepComplete) onStepComplete(i + 1, actions.length, getActionName(action), 0, times, 0)
- const waitTime = calculateWaitTime(action.data, action.delay)
- if (waitTime > 0) {
- let remainingTime = waitTime
- const countdownInterval = 100
- const stepName = getActionName(action)
- while (remainingTime > 0) {
- if (shouldStop && shouldStop()) return { success: false, error: 'Execution stopped', completedSteps }
- if (onStepComplete) onStepComplete(i + 1, actions.length, stepName, remainingTime, times, 0)
- const waitTimeChunk = Math.min(countdownInterval, remainingTime)
- await new Promise(resolve => setTimeout(resolve, waitTimeChunk))
- remainingTime -= waitTimeChunk
- }
- }
- for (let t = 0; t < times; t++) {
- if (shouldStop && shouldStop()) return { success: false, error: 'Execution stopped', completedSteps }
- if (onStepComplete) onStepComplete(i + 1, actions.length, getActionName(action), 0, times, t + 1)
- state.globalStepCounter++
- const typeName = getActionName(action)
- const result = await executeAction(action, device, folderPath, resolution)
- if (result.success && result.skipped) { /* 步骤跳过不写 log */ }
- if (!result.success) {
- const now = new Date()
- const timeStr = `${now.getFullYear()}/${String(now.getMonth() + 1).padStart(2, '0')}/${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`
- const errDetail = result.error != null && result.error !== '' ? String(result.error) : 'unknown'
- const errorMsg = `[sequence-runner] [ERROR] ${getActionName(action)} failed: ${errDetail} [time: ${timeStr}]`
- await logMessage(errorMsg, folderPath).catch(() => {})
- return { success: false, error: errDetail, completedSteps: i }
- }
- if (t < times - 1) await new Promise(resolve => setTimeout(resolve, 500))
- }
- completedSteps++
- if (onStepComplete) onStepComplete(i + 1, actions.length, getActionName(action), 0, times, times)
- if (i < actions.length - 1) {
- let remainingTime = interval
- const countdownInterval = 100
- const nextStepName = getActionName(actions[i + 1])
- const nextTimes = actions[i + 1].times || 1
- while (remainingTime > 0) {
- if (shouldStop && shouldStop()) return { success: false, error: 'Execution stopped', completedSteps }
- if (onStepComplete) onStepComplete(i + 1, actions.length, nextStepName, remainingTime, nextTimes, 0)
- const waitTime = Math.min(countdownInterval, remainingTime)
- await new Promise(resolve => setTimeout(resolve, waitTime))
- remainingTime -= waitTime
- }
- }
- }
- return { success: true, completedSteps }
- }
- module.exports = { executeActionSequence }
|