/* * BRLTTY - A background process providing access to the console screen (when in * text mode) for a blind person using a refreshable braille display. * * Copyright (C) 1995-2021 by The BRLTTY Developers. * * BRLTTY comes with ABSOLUTELY NO WARRANTY. * * This is free software, placed under the terms of the * GNU Lesser General Public License, as published by the Free Software * Foundation; either version 2.1 of the License, or (at your option) any * later version. Please see the file LICENSE-LGPL for details. * * Web Page: http://brltty.app/ * * This software is maintained by Dave Mielke . */ #include "prologue.h" #include #include "log.h" #include "parameters.h" #include "activity.h" #include "async_alarm.h" #include "async_wait.h" typedef enum { ACT_STOPPED, ACT_PREPARED, ACT_SCHEDULED, ACT_STARTED, ACT_PREPARING, ACT_PREPARING_STOP, ACT_STARTING, ACT_STARTING_STOP, ACT_STARTING_RESTART, ACT_STOPPING, ACT_STOPPING_START } ActivityState; typedef struct { const char *name; } ActivityStateDescriptor; static const ActivityStateDescriptor activityStateDescriptors[] = { [ACT_STOPPED] = { .name = "stopped" }, [ACT_PREPARED] = { .name = "prepared" }, [ACT_SCHEDULED] = { .name = "scheduled" }, [ACT_STARTED] = { .name = "started" }, [ACT_PREPARING] = { .name = "preparing" }, [ACT_PREPARING_STOP] = { .name = "preparing+stop" }, [ACT_STARTING] = { .name = "starting" }, [ACT_STARTING_STOP] = { .name = "starting+stop" }, [ACT_STARTING_RESTART] = { .name = "starting+restart" }, [ACT_STOPPING] = { .name = "stopping" }, [ACT_STOPPING_START] = { .name = "stopping+start" }, }; struct ActivityObjectStruct { const ActivityMethods *methods; void *data; ActivityState state; AsyncHandle startAlarm; }; static const char * getActivityStateName (ActivityState state) { if (state < ARRAY_COUNT(activityStateDescriptors)) { const char *name = activityStateDescriptors[state].name; if (name && *name) return name; } return "unknown"; } static void logUnexpectedActivityState (ActivityObject *activity, const char *action) { ActivityState state = activity->state; logMessage(LOG_WARNING, "unexpected activity state: %s: %s: %u[%s]", activity->methods->activityName, action, state, getActivityStateName(state)); } static void setActivityState (ActivityObject *activity, ActivityState state) { logMessage(LOG_DEBUG, "activity state change: %s: %u[%s]", activity->methods->activityName, state, getActivityStateName(state)); activity->state = state; } static void logActivityActionRequest (ActivityObject *activity, const char *action) { logMessage(LOG_DEBUG, "activity action request: %s: %s", activity->methods->activityName, action); } static void logActivityActionFailed (ActivityObject *activity, const char *action) { logMessage(LOG_DEBUG, "activity action failed: %s: %s", activity->methods->activityName, action); } static void logActivityActionTimeout (ActivityObject *activity, const char *action) { logMessage(LOG_DEBUG, "activity action timeout: %s: %s", activity->methods->activityName, action); } static void cancelActivityStartAlarm (ActivityObject *activity) { asyncCancelRequest(activity->startAlarm); activity->startAlarm = NULL; } ASYNC_ALARM_CALLBACK(handleActivityStartAlarm) { ActivityObject *activity = parameters->data; ActivityStartMethod *start = activity->methods->start; ActivityState oldState = activity->state; int started; ActivityState newState; setActivityState(activity, ACT_STARTING); started = !start || start(activity->data); if (started) { cancelActivityStartAlarm(activity); } else { logActivityActionFailed(activity, "start"); } newState = activity->state; setActivityState(activity, (started? ACT_STARTED: oldState)); if (newState == ACT_STARTING_STOP) { stopActivity(activity); } else if (newState == ACT_STARTING_RESTART) { stopActivity(activity); startActivity(activity); } else if (newState != ACT_STARTING) { logUnexpectedActivityState(activity, "starting"); } } static int prepareActivity (ActivityObject *activity) { ActivityPrepareMethod *prepare = activity->methods->prepare; ActivityState oldState = activity->state; if (!prepare) { setActivityState(activity, ACT_PREPARED); return 1; } setActivityState(activity, ACT_PREPARING); if (!prepare(activity->data)) { setActivityState(activity, oldState); return 0; } if (activity->state == ACT_PREPARING) { setActivityState(activity, ACT_PREPARED); return 1; } if (activity->state == ACT_PREPARING_STOP) { setActivityState(activity, ACT_STOPPED); return 0; } logUnexpectedActivityState(activity, "preparing"); return 0; } static int scheduleActivity (ActivityObject *activity) { if (asyncNewRelativeAlarm(&activity->startAlarm, 0, handleActivityStartAlarm, activity)) { if (asyncResetAlarmInterval(activity->startAlarm, activity->methods->retryInterval)) { setActivityState(activity, ACT_SCHEDULED); return 1; } cancelActivityStartAlarm(activity); } return 0; } void startActivity (ActivityObject *activity) { logActivityActionRequest(activity, "start"); while (1) { switch (activity->state) { case ACT_STOPPED: if (prepareActivity(activity)) continue; return; case ACT_PREPARING_STOP: setActivityState(activity, ACT_PREPARING); continue; case ACT_PREPARED: if (scheduleActivity(activity)) continue; return; case ACT_SCHEDULED: asyncResetAlarmIn(activity->startAlarm, 0); return; case ACT_STARTING_STOP: setActivityState(activity, ACT_STARTING_RESTART); continue; case ACT_STOPPING: setActivityState(activity, ACT_STOPPING_START); continue; case ACT_PREPARING: case ACT_STARTING: case ACT_STARTING_RESTART: case ACT_STARTED: case ACT_STOPPING_START: return; } logUnexpectedActivityState(activity, "start"); break; } } void stopActivity (ActivityObject *activity) { logActivityActionRequest(activity, "stop"); while (1) { switch (activity->state) { case ACT_PREPARING: setActivityState(activity, ACT_PREPARING_STOP); continue; case ACT_PREPARED: setActivityState(activity, ACT_STOPPED); continue; case ACT_SCHEDULED: cancelActivityStartAlarm(activity); setActivityState(activity, ACT_PREPARED); continue; case ACT_STARTING: case ACT_STARTING_RESTART: setActivityState(activity, ACT_STARTING_STOP); continue; case ACT_STARTED: { ActivityStopMethod *stop = activity->methods->stop; if (stop) { ActivityState newState; setActivityState(activity, ACT_STOPPING); stop(activity->data); newState = activity->state; setActivityState(activity, ACT_STOPPED); if (newState == ACT_STOPPING_START) { startActivity(activity); } else if (newState != ACT_STOPPING) { logUnexpectedActivityState(activity, "stopping"); } } else { setActivityState(activity, ACT_STOPPED); } return; } case ACT_STOPPING_START: setActivityState(activity, ACT_STOPPING); continue; case ACT_PREPARING_STOP: case ACT_STARTING_STOP: case ACT_STOPPING: case ACT_STOPPED: return; } logUnexpectedActivityState(activity, "stop"); break; } } ActivityObject * newActivity (const ActivityMethods *methods, void *data) { ActivityObject *activity; if ((activity = malloc(sizeof(*activity)))) { memset(activity, 0, sizeof(*activity)); activity->methods = methods; activity->data = data; activity->state = ACT_STOPPED; activity->startAlarm = NULL; return activity; } else { logMallocError(); } return NULL; } void destroyActivity (ActivityObject *activity) { stopActivity(activity); awaitActivityStopped(activity); free(activity); } int isActivityStarted (const ActivityObject *activity) { return activity->state == ACT_STARTED; } int isActivityStopped (const ActivityObject *activity) { return activity->state == ACT_STOPPED; } ASYNC_CONDITION_TESTER(testActivityStarted) { const ActivityObject *activity = data; return isActivityStarted(activity); } int awaitActivityStarted (ActivityObject *activity) { int timeout = activity->methods->startTimeout; if (!timeout) timeout = DEFAULT_ACTIVITY_START_TIMEOUT; if (asyncAwaitCondition(timeout, testActivityStarted, activity)) return 1; logActivityActionTimeout(activity, "start"); return 0; } ASYNC_CONDITION_TESTER(testActivityStopped) { const ActivityObject *activity = data; return isActivityStopped(activity); } int awaitActivityStopped (ActivityObject *activity) { int timeout = activity->methods->stopTimeout; if (!timeout) timeout = DEFAULT_ACTIVITY_STOP_TIMEOUT; if (asyncAwaitCondition(timeout, testActivityStopped, activity)) return 1; logActivityActionTimeout(activity, "stop"); return 0; }