diff --git a/addon/flamenco/manager/api/worker_api.py b/addon/flamenco/manager/api/worker_api.py index e81067c9..df717f6a 100644 --- a/addon/flamenco/manager/api/worker_api.py +++ b/addon/flamenco/manager/api/worker_api.py @@ -26,6 +26,7 @@ from flamenco.manager.model.error import Error from flamenco.manager.model.may_keep_running import MayKeepRunning from flamenco.manager.model.registered_worker import RegisteredWorker from flamenco.manager.model.security_error import SecurityError +from flamenco.manager.model.task_progress_update import TaskProgressUpdate from flamenco.manager.model.task_update import TaskUpdate from flamenco.manager.model.worker_registration import WorkerRegistration from flamenco.manager.model.worker_sign_on import WorkerSignOn @@ -344,6 +345,64 @@ class WorkerApi(object): }, api_client=api_client ) + self.task_progress_update_endpoint = _Endpoint( + settings={ + 'response_type': None, + 'auth': [ + 'worker_auth' + ], + 'endpoint_path': '/api/v3/worker/task/{task_id}/progress', + 'operation_id': 'task_progress_update', + 'http_method': 'POST', + 'servers': None, + }, + params_map={ + 'all': [ + 'task_id', + 'task_progress_update', + ], + 'required': [ + 'task_id', + 'task_progress_update', + ], + 'nullable': [ + ], + 'enum': [ + ], + 'validation': [ + ] + }, + root_map={ + 'validations': { + }, + 'allowed_values': { + }, + 'openapi_types': { + 'task_id': + (str,), + 'task_progress_update': + (TaskProgressUpdate,), + }, + 'attribute_map': { + 'task_id': 'task_id', + }, + 'location_map': { + 'task_id': 'path', + 'task_progress_update': 'body', + }, + 'collection_format_map': { + } + }, + headers_map={ + 'accept': [ + 'application/json' + ], + 'content_type': [ + 'application/json' + ] + }, + api_client=api_client + ) self.task_update_endpoint = _Endpoint( settings={ 'response_type': None, @@ -955,6 +1014,87 @@ class WorkerApi(object): body return self.task_output_produced_endpoint.call_with_http_info(**kwargs) + def task_progress_update( + self, + task_id, + task_progress_update, + **kwargs + ): + """Update the progress of the task. # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + + >>> thread = api.task_progress_update(task_id, task_progress_update, async_req=True) + >>> result = thread.get() + + Args: + task_id (str): + task_progress_update (TaskProgressUpdate): Task progress information + + Keyword Args: + _return_http_data_only (bool): response data without head status + code and headers. Default is True. + _preload_content (bool): if False, the urllib3.HTTPResponse object + will be returned without reading/decoding response data. + Default is True. + _request_timeout (int/float/tuple): timeout setting for this request. If + one number provided, it will be total request timeout. It can also + be a pair (tuple) of (connection, read) timeouts. + Default is None. + _check_input_type (bool): specifies if type checking + should be done one the data sent to the server. + Default is True. + _check_return_type (bool): specifies if type checking + should be done one the data received from the server. + Default is True. + _spec_property_naming (bool): True if the variable names in the input data + are serialized names, as specified in the OpenAPI document. + False if the variable names in the input data + are pythonic names, e.g. snake case (default) + _content_type (str/None): force body content-type. + Default is None and content-type will be predicted by allowed + content-types and body. + _host_index (int/None): specifies the index of the server + that we want to use. + Default is read from the configuration. + async_req (bool): execute request asynchronously + + Returns: + None + If the method is called asynchronously, returns the request + thread. + """ + kwargs['async_req'] = kwargs.get( + 'async_req', False + ) + kwargs['_return_http_data_only'] = kwargs.get( + '_return_http_data_only', True + ) + kwargs['_preload_content'] = kwargs.get( + '_preload_content', True + ) + kwargs['_request_timeout'] = kwargs.get( + '_request_timeout', None + ) + kwargs['_check_input_type'] = kwargs.get( + '_check_input_type', True + ) + kwargs['_check_return_type'] = kwargs.get( + '_check_return_type', True + ) + kwargs['_spec_property_naming'] = kwargs.get( + '_spec_property_naming', False + ) + kwargs['_content_type'] = kwargs.get( + '_content_type') + kwargs['_host_index'] = kwargs.get('_host_index') + kwargs['task_id'] = \ + task_id + kwargs['task_progress_update'] = \ + task_progress_update + return self.task_progress_update_endpoint.call_with_http_info(**kwargs) + def task_update( self, task_id, diff --git a/addon/flamenco/manager/api/worker_mgt_api.py b/addon/flamenco/manager/api/worker_mgt_api.py index 52beace5..bea95e86 100644 --- a/addon/flamenco/manager/api/worker_mgt_api.py +++ b/addon/flamenco/manager/api/worker_mgt_api.py @@ -44,7 +44,7 @@ class WorkerMgtApi(object): self.api_client = api_client self.create_worker_cluster_endpoint = _Endpoint( settings={ - 'response_type': None, + 'response_type': (WorkerCluster,), 'auth': [], 'endpoint_path': '/api/v3/worker-mgt/clusters', 'operation_id': 'create_worker_cluster', @@ -691,7 +691,7 @@ class WorkerMgtApi(object): async_req (bool): execute request asynchronously Returns: - None + WorkerCluster If the method is called asynchronously, returns the request thread. """ diff --git a/addon/flamenco/manager/docs/SocketIOTaskProgressUpdate.md b/addon/flamenco/manager/docs/SocketIOTaskProgressUpdate.md new file mode 100644 index 00000000..b267492d --- /dev/null +++ b/addon/flamenco/manager/docs/SocketIOTaskProgressUpdate.md @@ -0,0 +1,15 @@ +# SocketIOTaskProgressUpdate + +Task progress, sent to a SocketIO room when progress of a task changes. + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**id** | **str** | UUID of the Task | +**job_id** | **str** | | +**progress** | **int** | Indicates the percentage of the task that's been completed. | +**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/addon/flamenco/manager/docs/TaskProgressUpdate.md b/addon/flamenco/manager/docs/TaskProgressUpdate.md new file mode 100644 index 00000000..0c461753 --- /dev/null +++ b/addon/flamenco/manager/docs/TaskProgressUpdate.md @@ -0,0 +1,13 @@ +# TaskProgressUpdate + +TaskProgressUpdate is sent by a Worker to update the progress of a task it's executing. + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**progress** | **int** | Indicates the percentage of the task that's been completed. | +**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/addon/flamenco/manager/docs/WorkerApi.md b/addon/flamenco/manager/docs/WorkerApi.md index 0fbb0caf..440911d4 100644 --- a/addon/flamenco/manager/docs/WorkerApi.md +++ b/addon/flamenco/manager/docs/WorkerApi.md @@ -10,6 +10,7 @@ Method | HTTP request | Description [**sign_off**](WorkerApi.md#sign_off) | **POST** /api/v3/worker/sign-off | Mark the worker as offline [**sign_on**](WorkerApi.md#sign_on) | **POST** /api/v3/worker/sign-on | Authenticate & sign in the worker. [**task_output_produced**](WorkerApi.md#task_output_produced) | **POST** /api/v3/worker/task/{task_id}/output-produced | Store the most recently rendered frame here. Note that it is up to the Worker to ensure this is in a format that's digestable by the Manager. Currently only PNG and JPEG support is planned. +[**task_progress_update**](WorkerApi.md#task_progress_update) | **POST** /api/v3/worker/task/{task_id}/progress | Update the progress of the task. [**task_update**](WorkerApi.md#task_update) | **POST** /api/v3/worker/task/{task_id} | Update the task, typically to indicate progress, completion, or failure. [**worker_state**](WorkerApi.md#worker_state) | **GET** /api/v3/worker/state | [**worker_state_changed**](WorkerApi.md#worker_state_changed) | **POST** /api/v3/worker/state-changed | Worker changed state. This could be as acknowledgement of a Manager-requested state change, or in response to worker-local signals. @@ -485,6 +486,88 @@ void (empty response body) [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) +# **task_progress_update** +> task_progress_update(task_id, task_progress_update) + +Update the progress of the task. + +### Example + +* Basic Authentication (worker_auth): + +```python +import time +import flamenco.manager +from flamenco.manager.api import worker_api +from flamenco.manager.model.error import Error +from flamenco.manager.model.task_progress_update import TaskProgressUpdate +from pprint import pprint +# Defining the host is optional and defaults to http://localhost +# See configuration.py for a list of all supported configuration parameters. +configuration = flamenco.manager.Configuration( + host = "http://localhost" +) + +# The client must configure the authentication and authorization parameters +# in accordance with the API server security policy. +# Examples for each auth method are provided below, use the example that +# satisfies your auth use case. + +# Configure HTTP basic authorization: worker_auth +configuration = flamenco.manager.Configuration( + username = 'YOUR_USERNAME', + password = 'YOUR_PASSWORD' +) + +# Enter a context with an instance of the API client +with flamenco.manager.ApiClient(configuration) as api_client: + # Create an instance of the API class + api_instance = worker_api.WorkerApi(api_client) + task_id = "task_id_example" # str | + task_progress_update = TaskProgressUpdate( + progress=0, + ) # TaskProgressUpdate | Task progress information + + # example passing only required values which don't have defaults set + try: + # Update the progress of the task. + api_instance.task_progress_update(task_id, task_progress_update) + except flamenco.manager.ApiException as e: + print("Exception when calling WorkerApi->task_progress_update: %s\n" % e) +``` + + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **task_id** | **str**| | + **task_progress_update** | [**TaskProgressUpdate**](TaskProgressUpdate.md)| Task progress information | + +### Return type + +void (empty response body) + +### Authorization + +[worker_auth](../README.md#worker_auth) + +### HTTP request headers + + - **Content-Type**: application/json + - **Accept**: application/json + + +### HTTP response details + +| Status code | Description | Response headers | +|-------------|-------------|------------------| +**204** | The progress update was accepted | - | +**409** | The task is assigned to another worker, so the progress update was not accepted. | - | +**0** | unexpected error | - | + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + # **task_update** > task_update(task_id, task_update) diff --git a/addon/flamenco/manager/docs/WorkerMgtApi.md b/addon/flamenco/manager/docs/WorkerMgtApi.md index d108ca47..a6111337 100644 --- a/addon/flamenco/manager/docs/WorkerMgtApi.md +++ b/addon/flamenco/manager/docs/WorkerMgtApi.md @@ -19,7 +19,7 @@ Method | HTTP request | Description # **create_worker_cluster** -> create_worker_cluster(worker_cluster) +> WorkerCluster create_worker_cluster(worker_cluster) Create a new worker cluster. @@ -53,7 +53,8 @@ with flamenco.manager.ApiClient() as api_client: # example passing only required values which don't have defaults set try: # Create a new worker cluster. - api_instance.create_worker_cluster(worker_cluster) + api_response = api_instance.create_worker_cluster(worker_cluster) + pprint(api_response) except flamenco.manager.ApiException as e: print("Exception when calling WorkerMgtApi->create_worker_cluster: %s\n" % e) ``` @@ -67,7 +68,7 @@ Name | Type | Description | Notes ### Return type -void (empty response body) +[**WorkerCluster**](WorkerCluster.md) ### Authorization @@ -83,7 +84,7 @@ No authorization required | Status code | Description | Response headers | |-------------|-------------|------------------| -**204** | The cluster was created. | - | +**200** | The cluster was created. The created cluster is returned, so that the caller can know its UUID. | - | **0** | Error message | - | [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) diff --git a/addon/flamenco/manager/model/socket_io_task_progress_update.py b/addon/flamenco/manager/model/socket_io_task_progress_update.py new file mode 100644 index 00000000..6f98caf1 --- /dev/null +++ b/addon/flamenco/manager/model/socket_io_task_progress_update.py @@ -0,0 +1,277 @@ +""" + Flamenco manager + + Render Farm manager API # noqa: E501 + + The version of the OpenAPI document: 1.0.0 + Generated by: https://openapi-generator.tech +""" + + +import re # noqa: F401 +import sys # noqa: F401 + +from flamenco.manager.model_utils import ( # noqa: F401 + ApiTypeError, + ModelComposed, + ModelNormal, + ModelSimple, + cached_property, + change_keys_js_to_python, + convert_js_args_to_python_args, + date, + datetime, + file_type, + none_type, + validate_get_composed_info, + OpenApiModel +) +from flamenco.manager.exceptions import ApiAttributeError + + + +class SocketIOTaskProgressUpdate(ModelNormal): + """NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + + Do not edit the class manually. + + Attributes: + allowed_values (dict): The key is the tuple path to the attribute + and the for var_name this is (var_name,). The value is a dict + with a capitalized key describing the allowed value and an allowed + value. These dicts store the allowed enum values. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + discriminator_value_class_map (dict): A dict to go from the discriminator + variable value to the discriminator class name. + validations (dict): The key is the tuple path to the attribute + and the for var_name this is (var_name,). The value is a dict + that stores validations for max_length, min_length, max_items, + min_items, exclusive_maximum, inclusive_maximum, exclusive_minimum, + inclusive_minimum, and regex. + additional_properties_type (tuple): A tuple of classes accepted + as additional properties values. + """ + + allowed_values = { + } + + validations = { + ('progress',): { + 'inclusive_maximum': 100, + 'inclusive_minimum': 0, + }, + } + + @cached_property + def additional_properties_type(): + """ + This must be a method because a model may have properties that are + of type self, this must run after the class is loaded + """ + return (bool, date, datetime, dict, float, int, list, str, none_type,) # noqa: E501 + + _nullable = False + + @cached_property + def openapi_types(): + """ + This must be a method because a model may have properties that are + of type self, this must run after the class is loaded + + Returns + openapi_types (dict): The key is attribute name + and the value is attribute type. + """ + return { + 'id': (str,), # noqa: E501 + 'job_id': (str,), # noqa: E501 + 'progress': (int,), # noqa: E501 + } + + @cached_property + def discriminator(): + return None + + + attribute_map = { + 'id': 'id', # noqa: E501 + 'job_id': 'job_id', # noqa: E501 + 'progress': 'progress', # noqa: E501 + } + + read_only_vars = { + } + + _composed_schemas = {} + + @classmethod + @convert_js_args_to_python_args + def _from_openapi_data(cls, id, job_id, progress, *args, **kwargs): # noqa: E501 + """SocketIOTaskProgressUpdate - a model defined in OpenAPI + + Args: + id (str): UUID of the Task + job_id (str): + progress (int): Indicates the percentage of the task that's been completed. + + Keyword Args: + _check_type (bool): if True, values for parameters in openapi_types + will be type checked and a TypeError will be + raised if the wrong type is input. + Defaults to True + _path_to_item (tuple/list): This is a list of keys or values to + drill down to the model in received_data + when deserializing a response + _spec_property_naming (bool): True if the variable names in the input data + are serialized names, as specified in the OpenAPI document. + False if the variable names in the input data + are pythonic names, e.g. snake case (default) + _configuration (Configuration): the instance to use when + deserializing a file_type parameter. + If passed, type conversion is attempted + If omitted no type conversion is done. + _visited_composed_classes (tuple): This stores a tuple of + classes that we have traveled through so that + if we see that class again we will not use its + discriminator again. + When traveling through a discriminator, the + composed schema that is + is traveled through is added to this set. + For example if Animal has a discriminator + petType and we pass in "Dog", and the class Dog + allOf includes Animal, we move through Animal + once using the discriminator, and pick Dog. + Then in Dog, we will make an instance of the + Animal class but this time we won't travel + through its discriminator because we passed in + _visited_composed_classes = (Animal,) + """ + + _check_type = kwargs.pop('_check_type', True) + _spec_property_naming = kwargs.pop('_spec_property_naming', False) + _path_to_item = kwargs.pop('_path_to_item', ()) + _configuration = kwargs.pop('_configuration', None) + _visited_composed_classes = kwargs.pop('_visited_composed_classes', ()) + + self = super(OpenApiModel, cls).__new__(cls) + + if args: + raise ApiTypeError( + "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % ( + args, + self.__class__.__name__, + ), + path_to_item=_path_to_item, + valid_classes=(self.__class__,), + ) + + self._data_store = {} + self._check_type = _check_type + self._spec_property_naming = _spec_property_naming + self._path_to_item = _path_to_item + self._configuration = _configuration + self._visited_composed_classes = _visited_composed_classes + (self.__class__,) + + self.id = id + self.job_id = job_id + self.progress = progress + for var_name, var_value in kwargs.items(): + if var_name not in self.attribute_map and \ + self._configuration is not None and \ + self._configuration.discard_unknown_keys and \ + self.additional_properties_type is None: + # discard variable. + continue + setattr(self, var_name, var_value) + return self + + required_properties = set([ + '_data_store', + '_check_type', + '_spec_property_naming', + '_path_to_item', + '_configuration', + '_visited_composed_classes', + ]) + + @convert_js_args_to_python_args + def __init__(self, id, job_id, progress, *args, **kwargs): # noqa: E501 + """SocketIOTaskProgressUpdate - a model defined in OpenAPI + + Args: + id (str): UUID of the Task + job_id (str): + progress (int): Indicates the percentage of the task that's been completed. + + Keyword Args: + _check_type (bool): if True, values for parameters in openapi_types + will be type checked and a TypeError will be + raised if the wrong type is input. + Defaults to True + _path_to_item (tuple/list): This is a list of keys or values to + drill down to the model in received_data + when deserializing a response + _spec_property_naming (bool): True if the variable names in the input data + are serialized names, as specified in the OpenAPI document. + False if the variable names in the input data + are pythonic names, e.g. snake case (default) + _configuration (Configuration): the instance to use when + deserializing a file_type parameter. + If passed, type conversion is attempted + If omitted no type conversion is done. + _visited_composed_classes (tuple): This stores a tuple of + classes that we have traveled through so that + if we see that class again we will not use its + discriminator again. + When traveling through a discriminator, the + composed schema that is + is traveled through is added to this set. + For example if Animal has a discriminator + petType and we pass in "Dog", and the class Dog + allOf includes Animal, we move through Animal + once using the discriminator, and pick Dog. + Then in Dog, we will make an instance of the + Animal class but this time we won't travel + through its discriminator because we passed in + _visited_composed_classes = (Animal,) + """ + + _check_type = kwargs.pop('_check_type', True) + _spec_property_naming = kwargs.pop('_spec_property_naming', False) + _path_to_item = kwargs.pop('_path_to_item', ()) + _configuration = kwargs.pop('_configuration', None) + _visited_composed_classes = kwargs.pop('_visited_composed_classes', ()) + + if args: + raise ApiTypeError( + "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % ( + args, + self.__class__.__name__, + ), + path_to_item=_path_to_item, + valid_classes=(self.__class__,), + ) + + self._data_store = {} + self._check_type = _check_type + self._spec_property_naming = _spec_property_naming + self._path_to_item = _path_to_item + self._configuration = _configuration + self._visited_composed_classes = _visited_composed_classes + (self.__class__,) + + self.id = id + self.job_id = job_id + self.progress = progress + for var_name, var_value in kwargs.items(): + if var_name not in self.attribute_map and \ + self._configuration is not None and \ + self._configuration.discard_unknown_keys and \ + self.additional_properties_type is None: + # discard variable. + continue + setattr(self, var_name, var_value) + if var_name in self.read_only_vars: + raise ApiAttributeError(f"`{var_name}` is a read-only attribute. Use `from_openapi_data` to instantiate " + f"class with read only attributes.") diff --git a/addon/flamenco/manager/model/task_progress_update.py b/addon/flamenco/manager/model/task_progress_update.py new file mode 100644 index 00000000..494cc977 --- /dev/null +++ b/addon/flamenco/manager/model/task_progress_update.py @@ -0,0 +1,265 @@ +""" + Flamenco manager + + Render Farm manager API # noqa: E501 + + The version of the OpenAPI document: 1.0.0 + Generated by: https://openapi-generator.tech +""" + + +import re # noqa: F401 +import sys # noqa: F401 + +from flamenco.manager.model_utils import ( # noqa: F401 + ApiTypeError, + ModelComposed, + ModelNormal, + ModelSimple, + cached_property, + change_keys_js_to_python, + convert_js_args_to_python_args, + date, + datetime, + file_type, + none_type, + validate_get_composed_info, + OpenApiModel +) +from flamenco.manager.exceptions import ApiAttributeError + + + +class TaskProgressUpdate(ModelNormal): + """NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + + Do not edit the class manually. + + Attributes: + allowed_values (dict): The key is the tuple path to the attribute + and the for var_name this is (var_name,). The value is a dict + with a capitalized key describing the allowed value and an allowed + value. These dicts store the allowed enum values. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + discriminator_value_class_map (dict): A dict to go from the discriminator + variable value to the discriminator class name. + validations (dict): The key is the tuple path to the attribute + and the for var_name this is (var_name,). The value is a dict + that stores validations for max_length, min_length, max_items, + min_items, exclusive_maximum, inclusive_maximum, exclusive_minimum, + inclusive_minimum, and regex. + additional_properties_type (tuple): A tuple of classes accepted + as additional properties values. + """ + + allowed_values = { + } + + validations = { + ('progress',): { + 'inclusive_maximum': 100, + 'inclusive_minimum': 0, + }, + } + + @cached_property + def additional_properties_type(): + """ + This must be a method because a model may have properties that are + of type self, this must run after the class is loaded + """ + return (bool, date, datetime, dict, float, int, list, str, none_type,) # noqa: E501 + + _nullable = False + + @cached_property + def openapi_types(): + """ + This must be a method because a model may have properties that are + of type self, this must run after the class is loaded + + Returns + openapi_types (dict): The key is attribute name + and the value is attribute type. + """ + return { + 'progress': (int,), # noqa: E501 + } + + @cached_property + def discriminator(): + return None + + + attribute_map = { + 'progress': 'progress', # noqa: E501 + } + + read_only_vars = { + } + + _composed_schemas = {} + + @classmethod + @convert_js_args_to_python_args + def _from_openapi_data(cls, progress, *args, **kwargs): # noqa: E501 + """TaskProgressUpdate - a model defined in OpenAPI + + Args: + progress (int): Indicates the percentage of the task that's been completed. + + Keyword Args: + _check_type (bool): if True, values for parameters in openapi_types + will be type checked and a TypeError will be + raised if the wrong type is input. + Defaults to True + _path_to_item (tuple/list): This is a list of keys or values to + drill down to the model in received_data + when deserializing a response + _spec_property_naming (bool): True if the variable names in the input data + are serialized names, as specified in the OpenAPI document. + False if the variable names in the input data + are pythonic names, e.g. snake case (default) + _configuration (Configuration): the instance to use when + deserializing a file_type parameter. + If passed, type conversion is attempted + If omitted no type conversion is done. + _visited_composed_classes (tuple): This stores a tuple of + classes that we have traveled through so that + if we see that class again we will not use its + discriminator again. + When traveling through a discriminator, the + composed schema that is + is traveled through is added to this set. + For example if Animal has a discriminator + petType and we pass in "Dog", and the class Dog + allOf includes Animal, we move through Animal + once using the discriminator, and pick Dog. + Then in Dog, we will make an instance of the + Animal class but this time we won't travel + through its discriminator because we passed in + _visited_composed_classes = (Animal,) + """ + + _check_type = kwargs.pop('_check_type', True) + _spec_property_naming = kwargs.pop('_spec_property_naming', False) + _path_to_item = kwargs.pop('_path_to_item', ()) + _configuration = kwargs.pop('_configuration', None) + _visited_composed_classes = kwargs.pop('_visited_composed_classes', ()) + + self = super(OpenApiModel, cls).__new__(cls) + + if args: + raise ApiTypeError( + "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % ( + args, + self.__class__.__name__, + ), + path_to_item=_path_to_item, + valid_classes=(self.__class__,), + ) + + self._data_store = {} + self._check_type = _check_type + self._spec_property_naming = _spec_property_naming + self._path_to_item = _path_to_item + self._configuration = _configuration + self._visited_composed_classes = _visited_composed_classes + (self.__class__,) + + self.progress = progress + for var_name, var_value in kwargs.items(): + if var_name not in self.attribute_map and \ + self._configuration is not None and \ + self._configuration.discard_unknown_keys and \ + self.additional_properties_type is None: + # discard variable. + continue + setattr(self, var_name, var_value) + return self + + required_properties = set([ + '_data_store', + '_check_type', + '_spec_property_naming', + '_path_to_item', + '_configuration', + '_visited_composed_classes', + ]) + + @convert_js_args_to_python_args + def __init__(self, progress, *args, **kwargs): # noqa: E501 + """TaskProgressUpdate - a model defined in OpenAPI + + Args: + progress (int): Indicates the percentage of the task that's been completed. + + Keyword Args: + _check_type (bool): if True, values for parameters in openapi_types + will be type checked and a TypeError will be + raised if the wrong type is input. + Defaults to True + _path_to_item (tuple/list): This is a list of keys or values to + drill down to the model in received_data + when deserializing a response + _spec_property_naming (bool): True if the variable names in the input data + are serialized names, as specified in the OpenAPI document. + False if the variable names in the input data + are pythonic names, e.g. snake case (default) + _configuration (Configuration): the instance to use when + deserializing a file_type parameter. + If passed, type conversion is attempted + If omitted no type conversion is done. + _visited_composed_classes (tuple): This stores a tuple of + classes that we have traveled through so that + if we see that class again we will not use its + discriminator again. + When traveling through a discriminator, the + composed schema that is + is traveled through is added to this set. + For example if Animal has a discriminator + petType and we pass in "Dog", and the class Dog + allOf includes Animal, we move through Animal + once using the discriminator, and pick Dog. + Then in Dog, we will make an instance of the + Animal class but this time we won't travel + through its discriminator because we passed in + _visited_composed_classes = (Animal,) + """ + + _check_type = kwargs.pop('_check_type', True) + _spec_property_naming = kwargs.pop('_spec_property_naming', False) + _path_to_item = kwargs.pop('_path_to_item', ()) + _configuration = kwargs.pop('_configuration', None) + _visited_composed_classes = kwargs.pop('_visited_composed_classes', ()) + + if args: + raise ApiTypeError( + "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % ( + args, + self.__class__.__name__, + ), + path_to_item=_path_to_item, + valid_classes=(self.__class__,), + ) + + self._data_store = {} + self._check_type = _check_type + self._spec_property_naming = _spec_property_naming + self._path_to_item = _path_to_item + self._configuration = _configuration + self._visited_composed_classes = _visited_composed_classes + (self.__class__,) + + self.progress = progress + for var_name, var_value in kwargs.items(): + if var_name not in self.attribute_map and \ + self._configuration is not None and \ + self._configuration.discard_unknown_keys and \ + self.additional_properties_type is None: + # discard variable. + continue + setattr(self, var_name, var_value) + if var_name in self.read_only_vars: + raise ApiAttributeError(f"`{var_name}` is a read-only attribute. Use `from_openapi_data` to instantiate " + f"class with read only attributes.") diff --git a/addon/flamenco/manager/models/__init__.py b/addon/flamenco/manager/models/__init__.py index e4fe3a14..cdaf080a 100644 --- a/addon/flamenco/manager/models/__init__.py +++ b/addon/flamenco/manager/models/__init__.py @@ -62,11 +62,13 @@ from flamenco.manager.model.socket_io_subscription import SocketIOSubscription from flamenco.manager.model.socket_io_subscription_operation import SocketIOSubscriptionOperation from flamenco.manager.model.socket_io_subscription_type import SocketIOSubscriptionType from flamenco.manager.model.socket_io_task_log_update import SocketIOTaskLogUpdate +from flamenco.manager.model.socket_io_task_progress_update import SocketIOTaskProgressUpdate from flamenco.manager.model.socket_io_task_update import SocketIOTaskUpdate from flamenco.manager.model.socket_io_worker_update import SocketIOWorkerUpdate from flamenco.manager.model.submitted_job import SubmittedJob from flamenco.manager.model.task import Task from flamenco.manager.model.task_log_info import TaskLogInfo +from flamenco.manager.model.task_progress_update import TaskProgressUpdate from flamenco.manager.model.task_status import TaskStatus from flamenco.manager.model.task_status_change import TaskStatusChange from flamenco.manager.model.task_summary import TaskSummary diff --git a/addon/flamenco/manager_README.md b/addon/flamenco/manager_README.md index b8086d84..d7fd7920 100644 --- a/addon/flamenco/manager_README.md +++ b/addon/flamenco/manager_README.md @@ -113,6 +113,7 @@ Class | Method | HTTP request | Description *WorkerApi* | [**sign_off**](flamenco/manager/docs/WorkerApi.md#sign_off) | **POST** /api/v3/worker/sign-off | Mark the worker as offline *WorkerApi* | [**sign_on**](flamenco/manager/docs/WorkerApi.md#sign_on) | **POST** /api/v3/worker/sign-on | Authenticate & sign in the worker. *WorkerApi* | [**task_output_produced**](flamenco/manager/docs/WorkerApi.md#task_output_produced) | **POST** /api/v3/worker/task/{task_id}/output-produced | Store the most recently rendered frame here. Note that it is up to the Worker to ensure this is in a format that's digestable by the Manager. Currently only PNG and JPEG support is planned. +*WorkerApi* | [**task_progress_update**](flamenco/manager/docs/WorkerApi.md#task_progress_update) | **POST** /api/v3/worker/task/{task_id}/progress | Update the progress of the task. *WorkerApi* | [**task_update**](flamenco/manager/docs/WorkerApi.md#task_update) | **POST** /api/v3/worker/task/{task_id} | Update the task, typically to indicate progress, completion, or failure. *WorkerApi* | [**worker_state**](flamenco/manager/docs/WorkerApi.md#worker_state) | **GET** /api/v3/worker/state | *WorkerApi* | [**worker_state_changed**](flamenco/manager/docs/WorkerApi.md#worker_state_changed) | **POST** /api/v3/worker/state-changed | Worker changed state. This could be as acknowledgement of a Manager-requested state change, or in response to worker-local signals. @@ -185,11 +186,13 @@ Class | Method | HTTP request | Description - [SocketIOSubscriptionOperation](flamenco/manager/docs/SocketIOSubscriptionOperation.md) - [SocketIOSubscriptionType](flamenco/manager/docs/SocketIOSubscriptionType.md) - [SocketIOTaskLogUpdate](flamenco/manager/docs/SocketIOTaskLogUpdate.md) + - [SocketIOTaskProgressUpdate](flamenco/manager/docs/SocketIOTaskProgressUpdate.md) - [SocketIOTaskUpdate](flamenco/manager/docs/SocketIOTaskUpdate.md) - [SocketIOWorkerUpdate](flamenco/manager/docs/SocketIOWorkerUpdate.md) - [SubmittedJob](flamenco/manager/docs/SubmittedJob.md) - [Task](flamenco/manager/docs/Task.md) - [TaskLogInfo](flamenco/manager/docs/TaskLogInfo.md) + - [TaskProgressUpdate](flamenco/manager/docs/TaskProgressUpdate.md) - [TaskStatus](flamenco/manager/docs/TaskStatus.md) - [TaskStatusChange](flamenco/manager/docs/TaskStatusChange.md) - [TaskSummary](flamenco/manager/docs/TaskSummary.md) diff --git a/internal/manager/api_impl/workers.go b/internal/manager/api_impl/workers.go index 99dbe7a4..2deb18dc 100644 --- a/internal/manager/api_impl/workers.go +++ b/internal/manager/api_impl/workers.go @@ -373,6 +373,58 @@ func (f *Flamenco) ScheduleTask(e echo.Context) error { return e.JSON(http.StatusOK, customisedTask) } +// TODO: 1) Broadcast the udpates to the frontend client +// TODO: 2) Write tests for the following function +func (f *Flamenco) TaskProgressUpdate(e echo.Context, taskID string) error { + logger := requestLogger(e) + worker := requestWorkerOrPanic(e) + + if !uuid.IsValid(taskID) { + logger.Debug().Msg("Invalid task ID received") + return sendAPIError(e, http.StatusBadRequest, "Task ID not valid") + } + + logger = logger.With().Str("taskID", taskID).Logger() + + // Fetch the task, to see if this worker is even allowed to send udpates. + ctx := e.Request().Context() + dbTask, err := f.persist.FetchTask(ctx, taskID) + if err != nil { + logger.Warn().Err(err).Msg("cannot fetch task") + if errors.Is(err, persistence.ErrTaskNotFound) { + return sendAPIError(e, http.StatusNotFound, "task %+v not found", taskID) + } + return sendAPIError(e, http.StatusInternalServerError, "error fetching task") + } + + if dbTask == nil { + panic("task could not be fetched, but database gave no error either") + } + + // Decode the request body. + var taskProgressUpdate api.TaskProgressUpdate + if err := e.Bind(&taskProgressUpdate); err != nil { + logger.Warn().Err(err).Msg("bad request received") + return sendAPIError(e, http.StatusBadRequest, "invalid format") + } + if dbTask.WorkerID == nil { + logger.Warn().Msg("worker trying to update task that's not assigned to any worker") + return sendAPIError(e, http.StatusConflict, "task %+v is not assigned to any worker, so also not to you", taskID) + } + if *dbTask.WorkerID != worker.ID { + logger.Warn().Msg("worker trying to update task that's assigned to another worker") + return sendAPIError(e, http.StatusConflict, "task %+v is not assigned to you", taskID) + } + // for testing .............................. + + fmt.Println("----------------------------") + fmt.Print("Progress: ") + fmt.Println(taskProgressUpdate.Progress) + fmt.Println("----------------------------") + + //........................................... + return e.NoContent(http.StatusNoContent) +} func (f *Flamenco) TaskOutputProduced(e echo.Context, taskID string) error { ctx := e.Request().Context() filesize := e.Request().ContentLength diff --git a/internal/manager/job_compilers/js_globals.go b/internal/manager/job_compilers/js_globals.go index de9dddf9..ff3f1950 100644 --- a/internal/manager/job_compilers/js_globals.go +++ b/internal/manager/job_compilers/js_globals.go @@ -111,6 +111,11 @@ func jsFrameChunker(frameRange string, chunkSize int) ([]string, error) { return chunks, nil } +func jsCountChunkSize(chunk string) (int, error) { + frames, err := frameRangeExplode(chunk) + return len(frames), err +} + // Given a range of frames, return an array containing each frame number. func frameRangeExplode(frameRange string) ([]int, error) { // Store as map to avoid duplicate frames. diff --git a/internal/manager/job_compilers/js_globals_test.go b/internal/manager/job_compilers/js_globals_test.go index 9ce7eb06..f6103935 100644 --- a/internal/manager/job_compilers/js_globals_test.go +++ b/internal/manager/job_compilers/js_globals_test.go @@ -56,3 +56,38 @@ func TestFrameRangeExplode(t *testing.T) { 20, 21, 22, 23, 24, 25, 40, }, frames) } + +func TestJSCountChunkSize(t *testing.T) { + tests := []struct { + name string + chunk string + want int + wantErr bool + }{ + // Bad cases. + {"empty", "", 0, true}, + {"negative", "-5", 0, true}, + {"space", " ", 0, true}, + {"no-comma", "5 10", 0, true}, + {"no-numbers", "start-end", 0, true}, + // Good cases. + {"single", "4", 1, false}, + {"multiple", "4,5,10", 3, false}, + {"onerange", "1-4", 4, false}, + {"tworange", "1-4,10-12", 7, false}, + {"overlap", "1-6,3-12", 12, false}, + {"space-around", " 1-5\t", 5, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := jsCountChunkSize(tt.chunk) + if (err != nil) != tt.wantErr { + t.Errorf("jsCountChunkSize() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("jsCountChunkSize() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/internal/manager/job_compilers/scripts.go b/internal/manager/job_compilers/scripts.go index efba10e9..fec29d44 100644 --- a/internal/manager/job_compilers/scripts.go +++ b/internal/manager/job_compilers/scripts.go @@ -140,6 +140,7 @@ func newGojaVM(registry *require.Registry) *goja.Runtime { mustSet("alert", jsAlert) mustSet("frameChunker", jsFrameChunker) mustSet("formatTimestampLocal", jsFormatTimestampLocal) + mustSet("countChunkSize", jsCountChunkSize) // Pre-import some useful modules. registry.Enable(vm) diff --git a/internal/manager/job_compilers/scripts/simple_blender_render.js b/internal/manager/job_compilers/scripts/simple_blender_render.js index d980991d..75aec593 100644 --- a/internal/manager/job_compilers/scripts/simple_blender_render.js +++ b/internal/manager/job_compilers/scripts/simple_blender_render.js @@ -95,12 +95,14 @@ function authorRenderTasks(settings, renderDir, renderOutput) { let renderTasks = []; let chunks = frameChunker(settings.frames, settings.chunk_size); for (let chunk of chunks) { + const numFrames = countChunkSize(chunk) const task = author.Task(`render-${chunk}`, "blender"); const command = author.Command("blender-render", { exe: "{blender}", exeArgs: "{blenderArgs}", argsBefore: [], blendfile: settings.blendfile, + numFrames: numFrames, args: [ "--render-output", path.join(renderDir, path.basename(renderOutput)), "--render-format", settings.format, diff --git a/internal/worker/command_blender.go b/internal/worker/command_blender.go index 5b2cab2b..f319c158 100644 --- a/internal/worker/command_blender.go +++ b/internal/worker/command_blender.go @@ -7,6 +7,7 @@ package worker import ( "context" "fmt" + "math" "os/exec" "regexp" "sync" @@ -20,6 +21,10 @@ import ( ) var regexpFileSaved = regexp.MustCompile("Saved: '(.*)'") +var regexpFrameNumber = regexp.MustCompile("Fra:[0-9]+") +var prevFrame string +var renderedNumFrames int +var totalNumFrames float64 type BlenderParameters struct { exe string // Expansion of `{blender}`: the executable path defined by the Manager. @@ -27,6 +32,7 @@ type BlenderParameters struct { argsBefore []string // Additional CLI arguments defined by the job compiler script, to go before the blend file name. blendfile string // Path of the file to open. args []string // Additional CLI arguments defined by the job compiler script, to go after the blend file name. + numFrames float64 // Additional CLI argument defined by the job compiler script, to define the total number of frame that are needed to be rendered. } // cmdBlender executes the "blender-render" command. @@ -46,6 +52,8 @@ func (ce *CommandExecutor) cmdBlenderRender(ctx context.Context, logger zerolog. wg := sync.WaitGroup{} wg.Add(1) go func() { + prevFrame = "" + renderedNumFrames = 0 defer wg.Done() for line := range lineChannel { ce.processLineBlender(ctx, logger, taskID, line) @@ -138,6 +146,12 @@ func cmdBlenderRenderParams(logger zerolog.Logger, cmd api.Command) (BlenderPara // Ignore the `ok` return value, as a missing exeArgs key is fine: parameters.exeArgs, _ = cmdParameter[string](cmd, "exeArgs") + // Ignore the `ok` return value, as a missing numFrames key is fine: + parameters.numFrames, _ = cmdParameter[float64](cmd, "numFrames") + if parameters.numFrames != 0 { + totalNumFrames = parameters.numFrames + } + if parameters.argsBefore, ok = cmdParameterAsStrings(cmd, "argsBefore"); !ok { logger.Warn().Interface("command", cmd).Msg("invalid 'argsBefore' parameter") return parameters, NewParameterInvalidError("argsBefore", cmd, "cannot convert to list of strings") @@ -170,6 +184,22 @@ func cmdBlenderRenderParams(logger zerolog.Logger, cmd api.Command) (BlenderPara func (ce *CommandExecutor) processLineBlender(ctx context.Context, logger zerolog.Logger, taskID string, line string) { // TODO: check for "Warning: Unable to open" and other indicators of missing // files. Flamenco v2 updated the task.Activity field for such situations. + renderedFrameNumber := regexpFrameNumber.FindString(line) + if renderedFrameNumber != "" && renderedFrameNumber != prevFrame { + renderedNumFrames++ + prevFrame = renderedFrameNumber + var progress = int(math.Ceil(float64(renderedNumFrames) / totalNumFrames * 100)) + // for checking the out of progress + fmt.Println("---------------------------") + fmt.Println(prevFrame) + fmt.Println(progress) + fmt.Println("---------------------------") + + err := ce.listener.UpdateTaskProgress(ctx, taskID, progress) + if err != nil { + logger.Warn().Err(err).Msg("error send progress udpate to manager.") + } + } match := regexpFileSaved.FindStringSubmatch(line) if len(match) < 2 { diff --git a/internal/worker/command_exe.go b/internal/worker/command_exe.go index 0224a64e..6c36973e 100644 --- a/internal/worker/command_exe.go +++ b/internal/worker/command_exe.go @@ -39,6 +39,8 @@ type CommandListener interface { LogProduced(ctx context.Context, taskID string, logLines ...string) error // OutputProduced tells the Manager there has been some output (most commonly a rendered frame or video). OutputProduced(ctx context.Context, taskID string, outputLocation string) error + // UpdateTaskProgress sends the progress update of the task to manager + UpdateTaskProgress(ctx context.Context, taskID string, progress int) error } // TimeService is a service that operates on time. diff --git a/internal/worker/listener.go b/internal/worker/listener.go index f4b57538..4731d367 100644 --- a/internal/worker/listener.go +++ b/internal/worker/listener.go @@ -6,6 +6,7 @@ import ( "context" "errors" "fmt" + "net/http" "strings" "github.com/rs/zerolog/log" @@ -91,9 +92,35 @@ func (l *Listener) OutputProduced(ctx context.Context, taskID string, outputLoca return nil } +func (l *Listener) UpdateTaskProgress(ctx context.Context, taskID string, progress int) error { + return l.sendProgressUpdate(ctx, taskID, api.TaskProgressUpdateJSONRequestBody{ + Progress: progress, + }) +} + func (l *Listener) sendTaskUpdate(ctx context.Context, taskID string, update api.TaskUpdateJSONRequestBody) error { if ctx.Err() != nil { return ctx.Err() } return l.buffer.SendTaskUpdate(ctx, taskID, update) } + +func (l *Listener) sendProgressUpdate(ctx context.Context, taskID string, progress api.TaskProgressUpdateJSONRequestBody) error { + if ctx.Err() != nil { + return ctx.Err() + } + resp, err := l.client.TaskProgressUpdateWithResponse(ctx, taskID, progress) + + if err != nil { + log.Warn().Err(err).Str("task", taskID).Msg("Error communicating with the Manager, unable to send progress update") + return fmt.Errorf("%v", err) + } + + switch resp.StatusCode() { + case http.StatusNoContent: + return nil + default: + return fmt.Errorf("unknown error from Manager, code %d: %v", + resp.StatusCode(), resp.JSONDefault) + } +} diff --git a/internal/worker/mocks/client.gen.go b/internal/worker/mocks/client.gen.go index 7ef5594e..3751dd6a 100644 --- a/internal/worker/mocks/client.gen.go +++ b/internal/worker/mocks/client.gen.go @@ -1376,6 +1376,46 @@ func (mr *MockFlamencoClientMockRecorder) TaskOutputProducedWithBodyWithResponse return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TaskOutputProducedWithBodyWithResponse", reflect.TypeOf((*MockFlamencoClient)(nil).TaskOutputProducedWithBodyWithResponse), varargs...) } +// TaskProgressUpdateWithBodyWithResponse mocks base method. +func (m *MockFlamencoClient) TaskProgressUpdateWithBodyWithResponse(arg0 context.Context, arg1, arg2 string, arg3 io.Reader, arg4 ...api.RequestEditorFn) (*api.TaskProgressUpdateResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1, arg2, arg3} + for _, a := range arg4 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "TaskProgressUpdateWithBodyWithResponse", varargs...) + ret0, _ := ret[0].(*api.TaskProgressUpdateResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// TaskProgressUpdateWithBodyWithResponse indicates an expected call of TaskProgressUpdateWithBodyWithResponse. +func (mr *MockFlamencoClientMockRecorder) TaskProgressUpdateWithBodyWithResponse(arg0, arg1, arg2, arg3 interface{}, arg4 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1, arg2, arg3}, arg4...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TaskProgressUpdateWithBodyWithResponse", reflect.TypeOf((*MockFlamencoClient)(nil).TaskProgressUpdateWithBodyWithResponse), varargs...) +} + +// TaskProgressUpdateWithResponse mocks base method. +func (m *MockFlamencoClient) TaskProgressUpdateWithResponse(arg0 context.Context, arg1 string, arg2 api.TaskProgressUpdateJSONRequestBody, arg3 ...api.RequestEditorFn) (*api.TaskProgressUpdateResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1, arg2} + for _, a := range arg3 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "TaskProgressUpdateWithResponse", varargs...) + ret0, _ := ret[0].(*api.TaskProgressUpdateResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// TaskProgressUpdateWithResponse indicates an expected call of TaskProgressUpdateWithResponse. +func (mr *MockFlamencoClientMockRecorder) TaskProgressUpdateWithResponse(arg0, arg1, arg2 interface{}, arg3 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1, arg2}, arg3...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TaskProgressUpdateWithResponse", reflect.TypeOf((*MockFlamencoClient)(nil).TaskProgressUpdateWithResponse), varargs...) +} + // TaskUpdateWithBodyWithResponse mocks base method. func (m *MockFlamencoClient) TaskUpdateWithBodyWithResponse(arg0 context.Context, arg1, arg2 string, arg3 io.Reader, arg4 ...api.RequestEditorFn) (*api.TaskUpdateResponse, error) { m.ctrl.T.Helper() diff --git a/internal/worker/mocks/command_listener.gen.go b/internal/worker/mocks/command_listener.gen.go index 1581422e..ac418a44 100644 --- a/internal/worker/mocks/command_listener.gen.go +++ b/internal/worker/mocks/command_listener.gen.go @@ -66,3 +66,17 @@ func (mr *MockCommandListenerMockRecorder) OutputProduced(arg0, arg1, arg2 inter mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OutputProduced", reflect.TypeOf((*MockCommandListener)(nil).OutputProduced), arg0, arg1, arg2) } + +// UpdateTaskProgress mocks base method. +func (m *MockCommandListener) UpdateTaskProgress(arg0 context.Context, arg1 string, arg2 int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateTaskProgress", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateTaskProgress indicates an expected call of UpdateTaskProgress. +func (mr *MockCommandListenerMockRecorder) UpdateTaskProgress(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateTaskProgress", reflect.TypeOf((*MockCommandListener)(nil).UpdateTaskProgress), arg0, arg1, arg2) +} diff --git a/pkg/api/flamenco-openapi.yaml b/pkg/api/flamenco-openapi.yaml index b09395fa..153453c5 100644 --- a/pkg/api/flamenco-openapi.yaml +++ b/pkg/api/flamenco-openapi.yaml @@ -454,6 +454,37 @@ paths: schema: $ref: "#/components/schemas/Error" + /api/v3/worker/task/{task_id}/progress: + summary: Workers send the progress of the task, in the form of integer percentage value here. + post: + operationId: taskProgressUpdate + summary: Update the progress of the task. + security: [{ worker_auth: [] }] + tags: [worker] + parameters: + - name: task_id + in: path + required: true + schema: { type: string, format: uuid } + requestBody: + description: Task progress information + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/TaskProgressUpdate" + responses: + "204": + description: The progress update was accepted + "409": + description: The task is assigned to another worker, so the progress update was not accepted. + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + # Worker Management /api/v3/worker-mgt/workers: summary: Obtain list of Workers known to the Manager. @@ -1581,6 +1612,20 @@ components: type: string description: Log lines for this task, will be appended to logs sent earlier. + TaskProgressUpdate: + type: object + description: > + TaskProgressUpdate is sent by a Worker to update the progress of a task + it's executing. + properties: + "progress": + type: integer + minimum: 0 + maximum: 100 + description: > + Indicates the percentage of the task that's been completed. + required: ["progress"] + MayKeepRunning: type: object description: Indicates whether the worker may keep running the task. @@ -2233,6 +2278,24 @@ components: "activity": { type: string } required: [id, job_id, name, updated, status, activity] + SocketIOTaskProgressUpdate: + type: object + description: > + Task progress, sent to a SocketIO room when progress of a task changes. + properties: + "id": + type: string + format: uuid + description: UUID of the Task + "job_id": { type: string, format: uuid } + "progress": + type: integer + minimum: 0 + maximum: 100 + description: > + Indicates the percentage of the task that's been completed. + required: [id, job_id, progress] + SocketIOTaskLogUpdate: type: object description: > diff --git a/pkg/api/openapi_client.gen.go b/pkg/api/openapi_client.gen.go index b93f11cc..ab357ae4 100644 --- a/pkg/api/openapi_client.gen.go +++ b/pkg/api/openapi_client.gen.go @@ -292,6 +292,11 @@ type ClientInterface interface { // TaskOutputProduced request with any body TaskOutputProducedWithBody(ctx context.Context, taskId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + // TaskProgressUpdate request with any body + TaskProgressUpdateWithBody(ctx context.Context, taskId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + TaskProgressUpdate(ctx context.Context, taskId string, body TaskProgressUpdateJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) } func (c *Client) GetConfiguration(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { @@ -1182,6 +1187,30 @@ func (c *Client) TaskOutputProducedWithBody(ctx context.Context, taskId string, return c.Client.Do(req) } +func (c *Client) TaskProgressUpdateWithBody(ctx context.Context, taskId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewTaskProgressUpdateRequestWithBody(c.Server, taskId, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) TaskProgressUpdate(ctx context.Context, taskId string, body TaskProgressUpdateJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewTaskProgressUpdateRequest(c.Server, taskId, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + // NewGetConfigurationRequest generates requests for GetConfiguration func NewGetConfigurationRequest(server string) (*http.Request, error) { var err error @@ -3150,6 +3179,53 @@ func NewTaskOutputProducedRequestWithBody(server string, taskId string, contentT return req, nil } +// NewTaskProgressUpdateRequest calls the generic TaskProgressUpdate builder with application/json body +func NewTaskProgressUpdateRequest(server string, taskId string, body TaskProgressUpdateJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewTaskProgressUpdateRequestWithBody(server, taskId, "application/json", bodyReader) +} + +// NewTaskProgressUpdateRequestWithBody generates requests for TaskProgressUpdate with any type of body +func NewTaskProgressUpdateRequestWithBody(server string, taskId string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "task_id", runtime.ParamLocationPath, taskId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/api/v3/worker/task/%s/progress", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { for _, r := range c.RequestEditors { if err := r(ctx, req); err != nil { @@ -3393,6 +3469,11 @@ type ClientWithResponsesInterface interface { // TaskOutputProduced request with any body TaskOutputProducedWithBodyWithResponse(ctx context.Context, taskId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*TaskOutputProducedResponse, error) + + // TaskProgressUpdate request with any body + TaskProgressUpdateWithBodyWithResponse(ctx context.Context, taskId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*TaskProgressUpdateResponse, error) + + TaskProgressUpdateWithResponse(ctx context.Context, taskId string, body TaskProgressUpdateJSONRequestBody, reqEditors ...RequestEditorFn) (*TaskProgressUpdateResponse, error) } type GetConfigurationResponse struct { @@ -4209,6 +4290,7 @@ func (r FetchWorkerClustersResponse) StatusCode() int { type CreateWorkerClusterResponse struct { Body []byte HTTPResponse *http.Response + JSON200 *WorkerCluster JSONDefault *Error } @@ -4591,6 +4673,28 @@ func (r TaskOutputProducedResponse) StatusCode() int { return 0 } +type TaskProgressUpdateResponse struct { + Body []byte + HTTPResponse *http.Response + JSONDefault *Error +} + +// Status returns HTTPResponse.Status +func (r TaskProgressUpdateResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r TaskProgressUpdateResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + // GetConfigurationWithResponse request returning *GetConfigurationResponse func (c *ClientWithResponses) GetConfigurationWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetConfigurationResponse, error) { rsp, err := c.GetConfiguration(ctx, reqEditors...) @@ -5236,6 +5340,23 @@ func (c *ClientWithResponses) TaskOutputProducedWithBodyWithResponse(ctx context return ParseTaskOutputProducedResponse(rsp) } +// TaskProgressUpdateWithBodyWithResponse request with arbitrary body returning *TaskProgressUpdateResponse +func (c *ClientWithResponses) TaskProgressUpdateWithBodyWithResponse(ctx context.Context, taskId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*TaskProgressUpdateResponse, error) { + rsp, err := c.TaskProgressUpdateWithBody(ctx, taskId, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseTaskProgressUpdateResponse(rsp) +} + +func (c *ClientWithResponses) TaskProgressUpdateWithResponse(ctx context.Context, taskId string, body TaskProgressUpdateJSONRequestBody, reqEditors ...RequestEditorFn) (*TaskProgressUpdateResponse, error) { + rsp, err := c.TaskProgressUpdate(ctx, taskId, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseTaskProgressUpdateResponse(rsp) +} + // ParseGetConfigurationResponse parses an HTTP response from a GetConfigurationWithResponse call func ParseGetConfigurationResponse(rsp *http.Response) (*GetConfigurationResponse, error) { bodyBytes, err := ioutil.ReadAll(rsp.Body) @@ -6307,6 +6428,13 @@ func ParseCreateWorkerClusterResponse(rsp *http.Response) (*CreateWorkerClusterR } switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest WorkerCluster + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true: var dest Error if err := json.Unmarshal(bodyBytes, &dest); err != nil { @@ -6811,3 +6939,29 @@ func ParseTaskOutputProducedResponse(rsp *http.Response) (*TaskOutputProducedRes return response, nil } + +// ParseTaskProgressUpdateResponse parses an HTTP response from a TaskProgressUpdateWithResponse call +func ParseTaskProgressUpdateResponse(rsp *http.Response) (*TaskProgressUpdateResponse, error) { + bodyBytes, err := ioutil.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &TaskProgressUpdateResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSONDefault = &dest + + } + + return response, nil +} diff --git a/pkg/api/openapi_server.gen.go b/pkg/api/openapi_server.gen.go index d314ecbf..0521a9ee 100644 --- a/pkg/api/openapi_server.gen.go +++ b/pkg/api/openapi_server.gen.go @@ -173,6 +173,9 @@ type ServerInterface interface { // Store the most recently rendered frame here. Note that it is up to the Worker to ensure this is in a format that's digestable by the Manager. Currently only PNG and JPEG support is planned. // (POST /api/v3/worker/task/{task_id}/output-produced) TaskOutputProduced(ctx echo.Context, taskId string) error + // Update the progress of the task. + // (POST /api/v3/worker/task/{task_id}/progress) + TaskProgressUpdate(ctx echo.Context, taskId string) error } // ServerInterfaceWrapper converts echo contexts to parameters. @@ -950,6 +953,24 @@ func (w *ServerInterfaceWrapper) TaskOutputProduced(ctx echo.Context) error { return err } +// TaskProgressUpdate converts echo context to params. +func (w *ServerInterfaceWrapper) TaskProgressUpdate(ctx echo.Context) error { + var err error + // ------------- Path parameter "task_id" ------------- + var taskId string + + err = runtime.BindStyledParameterWithLocation("simple", false, "task_id", runtime.ParamLocationPath, ctx.Param("task_id"), &taskId) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter task_id: %s", err)) + } + + ctx.Set(Worker_authScopes, []string{""}) + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.TaskProgressUpdate(ctx, taskId) + return err +} + // This is a simple interface which specifies echo.Route addition functions which // are present on both echo.Echo and echo.Group, since we want to allow using // either of them for path registration @@ -1031,5 +1052,6 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.POST(baseURL+"/api/v3/worker/task/:task_id", wrapper.TaskUpdate) router.GET(baseURL+"/api/v3/worker/task/:task_id/may-i-run", wrapper.MayWorkerRun) router.POST(baseURL+"/api/v3/worker/task/:task_id/output-produced", wrapper.TaskOutputProduced) + router.POST(baseURL+"/api/v3/worker/task/:task_id/progress", wrapper.TaskProgressUpdate) } diff --git a/pkg/api/openapi_spec.gen.go b/pkg/api/openapi_spec.gen.go index c15a6b05..f62018b1 100644 --- a/pkg/api/openapi_spec.gen.go +++ b/pkg/api/openapi_spec.gen.go @@ -18,216 +18,220 @@ import ( // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+y96XIcN7Yg/CqIul+E7PiqihSpxWL/GbUWm27Z4ohUeyZaDhKViaqCmQVkA0iWqhWK", - "uA8xbzJzI+bH3F/zAr5vNIFzACQyE1kLJVK0+vYPN1WZieXg4OzLh0EmF6UUTBg9OPow0NmcLSj8+VRr", - "PhMsP6P60v47ZzpTvDRcisFR4ynhmlBi7F9UE27svxXLGL9iOZmsiJkz8otUl0yNB8NBqWTJlOEMZsnk", - "YkFFDn9zwxbwx/+n2HRwNPiXvXpxe25le8/wg8HH4cCsSjY4GlCl6Mr++zc5sV+7n7VRXMzc7+el4lJx", - "s4pe4MKwGVP+Dfw18bmgi/SD9WNqQ021cTsWfqf4pt0R1Zf9C6kqntsHU6kW1AyO8Idh+8WPw4Fif6+4", - "Yvng6G/+JQsct5ewtmgLLShFIIlXNazP69cwr5z8xjJjF/j0ivKCTgr2o5ycMmPscjqYc8rFrGBE43Mi", - "p4SSH+WE2NF0AkHmkmf4Z3OcX+ZMkBm/YmJICr7gBvDsihY8t/+tmCZG2t80I26QMXktihWptF0jWXIz", - "Jwg0mNzOHVCwA/w2suVsSqvCdNd1NmfEPcR1ED2XS+EWQyrNFFnatefMMLXgAuafc+1BMsbhozHTU4Rf", - "9oyUheGlm4iLeiKLj2pKMwaDspwbu3Uc0a1/SgvNhl3gmjlTdtG0KOSS2E/bCyV0auw7c0Z+kxMyp5pM", - "GBNEV5MFN4blY/KLrIqc8EVZrEjOCoafFQVh77nGAam+1GQqFQ79m5wMCRW5JSByUfLCvsPN+J2oEX0i", - "ZcGogB1d0aILn5OVmUtB2PtSMa25BOBPGLFvV9Sw3MJIqhw36M+BwU6aRxfWFc5m2EWNS7bqruE4Z8Lw", - "KWfKDRJQfkgWlTZ2PZXgf68QEd2h/eYuQnIeezGomiXuwlOxIuy9UZRQNasWlsJ4fJuUq7H9UI9P5YKd", - "4N1affMtyewxVJrl9s1MMWoYbtXdv1W0hvqK15RlBxTiiwXLOTWsWBHF7FCEwlZzNuWC2w+GlhDA9HbK", - "IcBEVsatiCrDs6qgKpxDDz7oauLJ5zqqmyBUp+7LcNV3HuHMfX7FNXeXbMcR/mq/5IUlwG0qbnHMrWxL", - "yntag6JFgKvJyD5BiCPOebCSZ5VSTJhiRaQlldSPC0gcEUs9Jhc/PD394cXz85fHr16cnzw9++ECBYGc", - "K5YZqVakpGZO/n9y8W6w9y/wv3eDC0LLkomc5XiETFQLu78pL9i5fX8wHORc+T/hZ8e05lTPWX5ev/lr", - "4o70nUuXhjoIRLuPLiZyCKrJ8XN/ZWDblnD8ubDrV2PysySCaUtOtFFVZirFNPkGOIQekpxndiqqONPf", - "EqoY0VVZSmXaW3eLH1rh4fDAbrqQ1AyGgNfbbjJCnfhmBmQcprinkcAymhSOXLhvLo4ILZZ0peGlMbkA", - "ug709OII0QO+dqTr7THycgCo4wCKfFPwS0aoBxqheT6S4tsxuViySWqYJZvUXAuwbkEFnTFL1IZkUhki", - "pEEG6mZBtgR4PCYXc57nzC5QsCumYOg/tXHZkUa7UmQy9kUADgiwdnZBiyat8adVAxRnGgDRcXAZDAdL", - "Ntl4ZmmM9EJQjScoPHNNfgIQKOSM3ABFpAvLtxISEzM0IXb9QPU8vvHAZchxhwRo4rhVQSesINmcihkb", - "4jLsyGTJC//zmJzZn7lGPiJFffiB7TKhK2U5C0UBLQgHzUnt/ahKYMfUsAZ5r2EIS9pNRvcTbK1fpGTY", - "jvjXIs6OQOHyojmHeBabCLZFhwRTf8W18RQKSG4/YnSRwIvv19v4WYMT9uy6niK1QXfhT6iZP5uz7PIN", - "005cbsn3tNKJy/C8/peFwXK+8qKAmVuE+0ZI862j00lhiYuy6pHO4RFi5JJq1CEs5k25yHEWT+KTA+tz", - "nDapkqDIM2dhoY6VSGXp1jgptAAzS64UBgkLncpK5Mk1aVmpbKPEER3JKX7QPlIEmltRGDbe89Ad2IYj", - "f8lFXp/4VvjXgzAJ1au7D0v1YkGCai0zTg2SZLubcyaurqgaOMToFyC8faFzHu4BUcxqFSBiU6JRmXVa", - "MdC79yyrDNtk9+g3KgTKHj32ME7TneiT1LG8UEqq7n6+Z4IpnhFmHxPFdCmFZikLTZ5A9R/Ozk4ImhGI", - "fSOI72EgcmxZaVZUOepbeClWhaQ50RKxOgAQV9uArVUSYWlcoMGDSzF+J57ZyR7uHwauA6IAaG7U0AnV", - "zD6ZVHpluRMjsFC/KMe8pDCUC0LJvTfMqNXoqdVj7+Grc0ZBL7TL4yLnGTVMO013OefZnBi+QFXRHgXT", - "hmRUWKFRMaO4VXpfSqsye7HEDcg1CC4WTagVjj0vv6cd37PvZgVnwgAXlETLBbOK4YwoRrUUQEdAnGLv", - "8fJwWpAJzS7ldIocM1iGvCjZNUstmNZ0lsK9FnLBudfvpzDrZUEXTGTyr0xpZ6hg7+miRNqIKD7477JS", - "nk9ZmjKXylz5DwaH4/3RhBl6fzAcJH4dPXw0mj14/Og+O8wfj3KuzMprwlvcpeZciRf6n7WA4V9sjekE", - "jxRsfkRjJC2K19PB0d/W075TLxTZrz4O2zySZoZfBdF+DZtEuU0b4r+wMpm3qyQ5Byr+KXJnH4AMxxdM", - "G7ooY/yyQtrIPkmNCYYedu6uB8vPaYIRH0+dBaBgMI1lcOELJ29yDTsKKyCWEeIdtNfT3z/7qTZSoQjq", - "kTLIRs2bsXblPAGIt2+Pn3vY/ghG1A32121Nv1bADJbfqszT53AWNi+neLb46njLTbU5vF2wP/R62sgk", - "HJDt14+/Ih7/uZDZZcG16ZdRl8DmtKPqigGtA8shy0nGFNBb8BCgJCst9dUly/iUZx45txIT4vW8EEat", - "UhJC96WO3Lne1I77Od/K3h7e7qFDrROoh44t6z0k5Lm7HsdiKhN3SEwloRNZ2Wth74blbhOGl6pmjXj9", - "7W1yD7pMXs/pgorzzApeMiU3x6LtKbxM/MuRwccvQLGFvGI5oYUUMzS0ew09IQG3ANReSw9oXlFt3oAg", - "yPLjBZ2xNIxeCFnN5rEQAUYFGvHakrOMESNnuMWcT6dM2Wd4gmBKtV8TSuZSm5FiBTX8ipG3b155zm1v", - "5ki55RBu1zMmZ9LKGmgcQhvJm1dD+5MVKgQ1jLwbfLAiy8e9D1IEg5yuplP+numP7wZIvJpnZT9ooqUq", - "klTIDdOQwDf4NVpHAVNFI/UcxU/MUCt9Aa/KczDo0uKked+6XKJhwVYTbhRVK7Jwg3noj8lPUoGIXRbs", - "fWxqc3LXQlq0Bp24suIkuaDjyTi7sDSoPnAL2EsGRu1IRimVhH0cDU5LxQ0jLxWfza0KVGmmxmxBeWFX", - "vZooJv7LxKmFUs38G07IOYUXyKn5v//nihURXBtwOnHutWdgPenSpNihuKDv+cKqNPf394eDBRf4r/2u", - "TNc6szBIz2GdRhaR9GEZVbGebwNj8+oWcAtUC0VmjwF9hCXQGfjb4T+XYjSlHN8If5RWmbR//L1iFfxB", - "VTbnV9GfaBvF4UdBQhjgplnF8HllD2YUz5bU7sIe+o4ARe20No7PIp+QU3/QFvZZBIE2KfRM2S2r70iN", - "VL0E0D0EChgstEMnRwVhyd6lSoNpFIm3fQspHcuJVao1shPBMqsRqFWKNLVI93lKnrr3zPON4+f3IhUO", - "hBKvNLVZTOwfHJOnPLe6Ja7Uf5JiR141dOzPs6Wpkouw9aStsecCn1F9qU+rxYKqVcqzvSgLPuUsJ4WT", - "i9C76aE+Js9Q9UT1Fh7WNm37kz8kRq2QS/Vll1XDV1tbVSC+wC14C4NeL4nX/7ViuOeIeoLbfXD00GqJ", - "NQfoo6kfhwPwuZ5PVhCXgKLnObg6HKL/6v8656JBXQJ5cJTj144S6NbyoSaV99Pq7yezqpe8MExZduMH", - "G3rG8+r4Ly9qvpN0oMrpVLPmQvdTC61B9WGHqAS9JXHv21Fsk99lV9GptW/FG2YqJdAFYzEMxUHqqSd3", - "EipsYRctIIqaaSN1PwL3WaEB9be9U6ikX/MuOa30mRRTPqsU9REezfVw/ZIrbd5UYp0ojiqyZXoc5U5L", - "66b2w9pI5eYjqhK69teEmAcQmyiZsiWZUks19ZA4l52QYgRhGlYUzuL1Aj8gUgXNLrhxJpYdE7YojaW+", - "9i0zZ+Dgq4pc3DNkwnpd90DyX4CZK99KAYFVGEWFnjJFnp4cg//ZuzHStnaN3PCVzGg6tuZ54B7Amizj", - "sZcC5nIfjzdq2e1Z2rsbxge8Bkv+ShX3roY2gpybpVzSBBt6LdhoSVfkyn2MzjULt4XUBmzV0t5HhiZI", - "8ExbzmUFnLKgGbhakUdefLDy7ccLp+VwhWExXnqYgy/fCQaU+FjA4FCh3vxNzpYysSZaaOknzTs+3SCo", - "MLf8sqDGKj2jYDjAIB3g7G6QySosug/R4KPNerozrteA9l9ucV5Pq5wz0XRMOBOJUxx0UjxtDaPXcal1", - "FKqNPh0e9hMtSwtjOGV/KMRuGeJ1TIgC4hiTl9jw6i+MlW8qIZJRfsfBdL6MLi7CgCzoilwyVlqiJLz8", - "lpZ2Fp15ugday+w9AjgK+2+C7rBmtd4tEYv2tV0yaJJLh9fHxtE2FJ7njFzgI8ud2AWxW3Fm1DjQDK+P", - "nQTgPZP2v4K9N84jj0T6wvLqiyG5aALhgvz09vTMar4XEHjVg+gtdG4BMkCtD0YpLA++uWPvXG3pr86R", - "uf5itVxvieFv3Vf8xVy6oLSwfDNHcR7Z7Ryxb9jMsm3FcqS/XUjSPFdM6x3jnR39Td80OTVLqtiaa7iJ", - "av0Sbg7KdSHc4TzYSfVu4vAnRUw7BuBBFUdNe0AMBxnGy8EKBxEUelafOq1TllWKm1Xw07Yo4LYOu3We", - "ulNmqvKp1lwbKgwKnykXdyzkyYmV7by6DHKXHYWEYbrU2hnIXoAPnG4RBNnv9P9Sglp3C0l4gjj3rNdc", - "fspA/Xd2E2f/5oqc/vD04OEjvPa6WgyJ5v+AoMLJyjCNAlnOtF0eKdyivPO8a+BoGTNhNvA1IvkZ1OG1", - "45lEIXRwNDh8ONl/8OR+dvB4sn94eJjfn04ePJxm+4+/e0LvH2R0/9Hkfv7owX5+8PDRk8ff7U++23+c", - "s4f7D/LH+wdP2L4diP+DDY7uPzh4AM5KnK2QsxkXs3iqR4eTxwfZo8PJkwcHD6b5/cPJk8PH+9PJo/39", - "R0/2v9vPDun9h4/vP86mhzR/8ODg0eHDyf3vHmeP6HdPHu4/flJPdfD4Y1fn9xA5SVJb+2skPXpFyPHr", - "OOLZjwP8HKRJZ+B3xv22NQpoONVBKULHYzTJmBwLIoucKeJcxdob991YMK/lAL9VGn0D78J2yPHzdwO0", - "C3nt2I1CeIg2oLgK0NUunMllpItqtqczJtjIUq89DDAfHT+/6ImocyizpeKLa3/JC3ZasmyjDoyDD5vH", - "tPk21dw/ZYK1z9Cg1jqVVOrINdDD+UbbiAGKswN97SAycyqc663pvqa6MSj4xVwkJPVh//U1JmeRdPHp", - "yNdj0GwEd2x3JOGouwTOqWDUS10UKa+jVW7RER1OS4otb7Ksx0NTRj1icAemzOxzmlhhk9TGYybHADrz", - "oWsZY00aPdjogLGrceMN+4XdJoB/4WZeO1e2ArVXwjMgZ5Me0A+dmDokOSuZyCHlSoCGh+LMV34228qe", - "0XH0uGI6pxpbrdcdb8dnVolLIZcCwi8KSXPUxzCCJWkWwMHe4Gogu8fpadcWPEDQaMCuV5a4IaHhVgSE", - "W2Bv/YffPC8MQExzNTwtELMpUdFnnqUM46N0tgnZvO5MXVm54yUMFcJwANEsJ3Gv2d/YexeUGeT6OPjz", - "tnCgvpjhPtwMWsQThev2mXElIt+fijWYHtskHG2HLp7/rjz3cxHCtURPsfx0k+bWZiUaPqs5Fs2tUOx0", - "uihMjDqrKnlX7e8fPAr2YCedVdpifsfQbKQbMDEXClPhHjgB6p5uujtSnm4aWXh3sMQGw/DH4aCIALSj", - "reUWXCWtUy9qDTlsvWEIaa4piR0yu2Tm+PWPcvIWfL/J1ETNTMgJHxJtpWx5xRTxX3tnAyRvgc1Sj8lL", - "K+SwJfgXh1YdYldcVvoccfUixKV50pc60X/6qFVv92sO9DNdxJmi6bzkBrh38t3GIU8ha/Fh0iOu2FQx", - "PT8PARBrbfhROL3T+N33GHqBu7mnMQijdowCwmHWodYu1FZ7JxT8ExycNJtDdsAVzyuKkRxkCbPMmGAK", - "7fqSLKhY+UFcDnqpaGZ4RoteP+juQOyvGLFrVPHWOLek+txFk/aUZsArGkwc7uX6jtiLbqRzcjT8Ho7g", - "25chasAe1j2e3yNTzorcfTv0kksd9gpu562cIbwn9tkVuYjKYDSRbh1Zi+NR++ibw1GpahxNBI6GXBoP", - "QLfSdJbfljHKZl4tJgLCGTdiVjq0NpX/V0cx419hknWQslS+v7jFKRPgxg0EH2+xJlSTiz0dfXtB2BVY", - "YaBigJEuU9iLydGb9qEFpruKY/LMj4kJzjNm4udoewNfn73Y/gL7fxdypjGuQTDmkr7KgmfcFCs/7YQh", - "VwLPun20GoaNZNSFw4R37RhSYJzaN0bCehpTTz3K/CYn34LyZl+3r9zTdj0EvJb2sqZYmyw3Sn2Jo3nt", - "fZfb1kRIDeIzSb0npp9LYaqTkU2o7JFK1D9YSW28mZe1EFWW60onrN96pLaHZUC4af2vpMbeB4oEraSG", - "XHJ7otOdYBAicIviRzmBzI2i+CUEGTheTfVlIWf4ML7Wa1d9RvXlKznro2Jn7hKQbF6JSyekQbhHuLNK", - "ygXJGXLkHB+6VD+7JLit9Ery3H6c46ab7DKFx3YnXaeVXURAIre0MfmJrkKi36IqDC8he04wtMSz9ybp", - "Cva0bC2qnqGzbzcsrKmk3cY6TLTDbyMhnwEk+0VkAEZHRnZRp9cTkuNMtJ3l0O3ANtyFq22WWZ1j9lOF", - "1madrut8c1OyWEq0CazZ+bDXpnmtwUQkJ9vgIr65Dhtd7I/Hx14NLK14efkc+SYzdWy3G9fKSVF60+fR", - "nFz4xBY4a8/tXDOWMnfQOh6T63i99n2fKB5Vcthu7ZtRf+lX/6nI3wnM+ISvzrOQebHtx43QpJtVa7ZO", - "CN5wu/w4ycsVJ/smq8DUfvuoXIqRdcpC0067TfD9pyc0uQeHv/8P8h//+vu//f7vv/+v3//tP/719//9", - "+7///j9jpQnU9zgQ3c1yni3ywdHgg/vnR/AMV+LyHE21h3ZPxmrH57TKufSh6lNeMBdhsId60p6e7v0m", - "Jxo93fcPDscwZHzIJz9/b/9Z6sHRwYPhYKrowtKYwf3R/f3BcABqlj6X6vyK50wOjtwvg+FAVqasDFaZ", - "Yu8NEy4lfVy6qDnYinuruy6cKaxsLw0uVw6rM56S0qwdz9U4w+JK57WRcFBwUb2PMBoCekcO1E6/7GbO", - "x5izQScMOX7bVsTcYM2JEWSTocO/WocFbWUeqZOieqDWiZxGsV/MiF5pwxZ1Qqb7tlXwCJKlMjkTXLOu", - "5dm97KxPELJRyCVTo4xqFiI63BR+US76/h0e6LvBkLwbLLnI5VLjP3Kqllzg37JkYqJz+w9msjE5DVPJ", - "RUkND1Uuv5f3NLlQlQAN8fvXr08v/kRUJcgFhJ7KguRcG8haglhvq3/SkMRUSg01r8IiLfd+qr1pnhbE", - "7mjY2Ad5N0BtXL0b+LgJV6wTbaFe2oRqW6WCfGWqybtB0xDvx3s3qGG/kNpq2qDwXzJimDZ7OZtUM1fE", - "SxNGNYdyWU5P99ltGNjLM5LLDMokQiJ6UTR2llQL+ixs9ofz7StuDUkmSx773i7adZfGdrSLUIWxW7Pr", - "zP2rTra2FJ/lhDuzEZrJcsm0uGfIgpoM069pZipahJE6MUtnWP0RjCq6XcoL8EgWeZQe1Cz/2a6kFsqB", - "euvVO3HcWKCV5hbI3IZ1GAFUb1mVVGuvgfSl22dFpQ1LVLZB2YE8w+doN3G30FfnqXMJna3SDUaOn4cM", - "Bmd9dCo1etmoCW968FuSk1cFkgO7NIyvAIsmJsJIFW3UYpuvlmDR0n8RVtQ0/G+lWjo5pGu9TBC9lESS", - "LvF85vVpLOoMqUDaOxt9ZJMvujMkfMzGZMKmUrE6oyDKKBnvpkx+zsLQN1HkBBMRzyerc5/YsUtKplMs", - "EmvdUvHdQUcG1cTIyuLpBpEZVTWxCkqK/b88oKdP0dhNQfnydbNvqraKJ0W7nPi29VjaKnyqZHdcmDtc", - "pg01up1tb2NBEfBNSFefOzLdfZITIh3IZQkNxCK1jHjDRnBSF1MiW93GmStVpCd+++ZV7KCtZyfcaFZM", - "Q9CnXIpC0nybZI3a1BdOEWt0wP77TmX34gqhjEJIj9Zyakbt6gopU2894V2qhBDf6muUQoiT3buKdaUN", - "Yd1qMDW6Y30i2ShHWzuJQRzuYv+Ohsq7RAyva13ckiL5mfpOap17AZ8FhzzkKDuxzkhHpVE1Q8xzEUHg", - "mQOKBScG1fVQ5IPKxk+tpB9OD4LhZIm5lX8i0tlZWi/wmYAYjW9AvpE+OfXC01tnNxfSEKaoSwIM5dfa", - "Urxd1rebDOvddN6CC1dJ3QU5QND5PU2yUK4bc3F5XG4JyDV5fcXUUnHDULbnstJgQhVRlThfaicpPqSc", - "Lq/kzDlTAg1Av46Xin2Vb7toOBWYkFFV8J66qqZBAnegEknkqhPfkrqBYhDBnzHQEUGZ5wITmHGcRFz0", - "upy5T6MCay6ZnzR1ieo9bldl0NlUQzWQTnolajaJ2BqnSmmUEZFTcu1vJFR9X7DFBE92KyEYP3XjJuXg", - "8jwCeEtMOSHuWcdQvzaQbjtrT/9Yn56QaJymtRk0oJNtRX4jSDUi8qKalclUxI+/dopwuQo0TdboKW+N", - "cs/6NG+vctcVAcfkKVoHqAi01pIfCPFZ+RQG9xk3kbUMCroAARkHHdzJYiVVYJgKfl2PuURz+xsVDKic", - "q9xeW8q7220ULrTD55J8f/KWoMU0kNMXL/764sV4EKzg35+8HcFvXZtqq3PLzj4nt5cxeYab9aaDVpUk", - "Cs5c9zJIyg6WFOxciopcLggMHGiy69m0lYlhW2LVWwC1gSpNF06iRgzGRBrpU+4biKE75MbtxCJH84Td", - "F+c813Z1Dw7vH+SPvstGjD7KRw8ePno0ejKZPhqxJ9P9JxP24LuMTRLFhBqjRPd7c5TVutDveNSNEHvl", - "6m32k+jPQWc/9i7j1Tb1PrtMcldjSJsnrYegH70fepj6HgXvdOoRu19GwTua8KJolikGkrEcCWlGhhXF", - "iIqVFCxO8j4aHI4P+ujr0d+848tetumiZDPX0GRUd7QYDAcLrrMECl4zC98t/MPnZ15tfQxnasaAp6YY", - "biIRp3wmXvcc1tOTY2hkFUH9vC4TrZd0NmNqVPHbPoTuYm4e4p7vp4HcWdEagBeMlafObJ0I67CPg1nb", - "J2GgBcjX8zk1lgVTkRMmcgxuCKqJD34PdexyumqaWMLYlpSDjWNMnpZlwZkL8MDgDmk/5GByvsjpSp/L", - "6fmSscsLSGqEd5q/25d9EHBihaDOCXLwYDSXlSI//HD00091WbOOrBCNPDgaLCQxFYFsEQi+y89BYT4a", - "3P/uaH8fS3M4e41zXGu7Av/W/hP7VldYaEzSzfykGRtpVlKFYXRLOSoY9NXxpWkd1K1EYMcC2szYZQ+Y", - "yTfvBguJzkNTeb/ht2PyAhwVC0aFJu8G7IqplR3PF6DtIGq9/4gpAkB76qt40HxIR7wHQG0eri0Sh7GH", - "TWg2xo1WvOZeGGpYnznMRa+ouIjQ9tEvSWNWNNhWi8pbNDLktdElvWRd5LpOmM72yV6N7+IwWQt1TGnF", - "dQ0HVFuSYg8BSpwMB4Zp94qcTgsu0kG0/TFAvQIkEqvaUuSkyTrdGVIOXMRjwpinzwv6j9X6lKpmbSin", - "sKD5Je50B0SqdpGiuFGbbJyFSpMpF1zPW87OnfNBtjnFYdjfmvPsM5/+mWqerdEOr20Z/XKRc5+rTNFn", - "i2uLhIkmIP5aB4uERLGWSqRCYa9rWHA3ywzeRbydpalZdfbDdR1G6YSThOHiDN3UQSmMsPIjSsVQYcnK", - "PItYTzmnVarWwVvNFNTCc6l8DvGOnw9JSbVeSpX7RygGu5KHVsjx9sVaDbGICYCBi22vUb3TuTHl4ONH", - "6FeFDjmIWc9MJAOHEz9jdOFcSfilPtrbm/qYQC73unX+MNyfvKRq4bJjIPtzMBwUPGMuIT3YNF5dHXbG", - "Xy6X45moxlLN9tw3em9WFqPD8f6YifHcLLDeOTdFY7WL0PKlFtjvj/fHIAXJkglacmz1Mt53JRXgZPZo", - "yfeuDveydoXUGSo2oaTecQ5djEyzlKpFGcxmh9EO9vc9VK2kbzHYCpqYzLr3m/NwId5umcvbnA8Orwl0", - "YbG6CFn1iIKertoVo52nWWxr2mnoZuhMY10vQ0E3qcd4IfJScpepOHPdeDsDdnJKLeST4N2D0Js9ryr1", - "AfslF/mfQ32sEyyCcWPgTrcTS8D7paxEXS4LZODQwK3ZqfmzrAvrtCXWcRoaNi0tg18qCc2cGyf3krvc", - "LanIQipGnr069u3D0JkCcWqaLClEuIE05beTQopS6sRJQS2lxFEBq/mzzFefDRqtmpAJsPjGaVI5XxxE", - "BmEdRIlBX5jee/N41Kgx113pz82LO8RFYlgaHOmUC3b3cOqvtODgEKUxNl0HmVp46ryqV/X4vo1rfZAb", - "iQpWXBhFgbtrULZRQeKLYu3JreHnPwViYqGNGiObdTg2sLsdxulFRqgtta0U8RILUX3Ske/QW+XjsDHW", - "ii6K5lhtuXgTgrQP4g20JrxiacGjKyesPY2nWcZ06C+fKgyfGDIEbwtpCG7sHvjcX5dMPD059inXRSGX", - "KFlf+D7Me06SdAd6QUqaXdrDfif6j1szU5Uj6kuV9pOdU3rFktVRb4bwJKdKMs0YrJZ20ytE7xZSPkjk", - "gLWQASLGl2xCy9KbK3KrIk2roqirYvhe+1auvHuk5G0d8tNTpQeLpyqGTI5D7Uy7wxWZVgJbsRfQK2oD", - "eluESGF2bxHcfhxscL69D75wzse9D95p8nEdSWoww2afV6uAcws7V4nOqXBRaZ5acXbW6F1UnG65IqvF", - "JyaMnD/9E7ap1683yEzTJah2p5heS2vViyoapasandnjolX2S2cS8DWrLHKGglVo6ttRv1u3nEZHo946", - "Vv2oGpKWdsfSulnBf2LoNTagPwE56yJnbfMBeat9l3gWhHaa5yNkJmuy1pCMhj4HbIIZWlMKLRIt40gl", - "d5AJ1XUh2omSS91I37o+xtd73B3HfVefHs4PyTFYHOtGWH2jqW/3kH+UE1fqY8FNBz1vUuNYsyAwrldW", - "wkPe6bK6rKjmwk+jklcaoP3g/sHNywhngaKG9DVm6Ayy3FxjbZ/m1nwhmeTGNaRZFiuSV6zVfDuj2dwj", - "XxgK7oOUpLCiCcqdtyYewQPiq/s3KQHimAsGg/L3UnXuSNSWPpZ9sEVVY7gfmzl/zF3KzqVC1X6LqwV6", - "7Ze9X1m0hHXX60E6F3/HCxGyMy0Vxe5/cytQ/vz6DLMhXck/3uxZPyRmLqvZ/D8v1B/lQgFabbhOgP1h", - "33YkMKVBMbAltydu6oBOnrhmjeJw/WZ5ZrL594Wc0EaJJ0jxulku0lcobguBZpi+cme+7p1PX4bbQ8Uq", - "2WG5Ry6CvsyQ9cvUFdN9dfb0huN7DQ1QsCdnnSU0A0D3LKd1fn/3TTPTZBJaErriXTdBIeu+nSmtu11W", - "HuOzoEUjlgAY37ZQ0ujR2I9FANXIGOqiwjHZGooW8KklYUB1gIy51ojw4fjO0Bq4t6HKggX8dghZd9Gc", - "QuNOCAcXOdESAm+6aGgp7t4H+9+f6YKt1eZcEYKtdDk/4J1RrdqlFHqlAnzWJh0uxjHwKAtTaIUXILHh", - "fKL02ahsdajckDwXvcVp6MEtAi2pkIaXwm50AoARKuM7KAVBOc6tgVhPFdhuGK8Lwg8YFPKxrh/WBeRz", - "+B0Vvc1YHVJ2+3F6U9jKr9sIl8+RBEV0LFSVDqUzjOKzmWUwt0u03gr2vsSaIhCx13UnYLRdWLAvXjEk", - "XGRFlaM844orY5dRy8HlDFsdoJTsypGEQRZ0FcLonB2BZpczJSuRj8nPMrT30iGjxRV8I9+smPm2aWMI", - "mNUvMn1RjLgVbZ77ur1tptOSaX6Tky00Q/xI5CQKne+7j3uTQmaXRUgiSd/MN9CQ/Uc5+XN4+zYP5EYk", - "rnorKa2rKi3+frN05RIx5XxVsm9defBGi3q4A364LZ0//m7SLGMlVJxhwijOnB4KZMVNcteIil1UWK3r", - "hmLvfASCXe/3l8Grm7voa5EL1J81CGY1opk0CM+orAvc/ruECkijQGtr5pvVjW38HgBNcgnxb64Zediy", - "bu5wvdSBTu2AanEN9n6pYxcFva0uo3b+NSDlH9wK0Dzqa1gEkoOGugjrEUgzE1cA6TGngiZwUpfZ+IOz", - "SL8Tl2vTY50UbEk8bMbXM+D6iUJWMdWBMaKp9eCgr8KNb/ntl+CDV/D7EPr2hYnmGmQNkkC9BQeGpot6", - "I4LWaRHr0PM0lIP5YyNnoypSD2o2U4DAoQpruSaanjaGuw6SNhfkMBWMzeGwfd6RDu3DguT/B0Hj5iZ3", - "QeLQMmgtez6Dt74Ongx7CSk4aVkRYcyZjqsT6Y7kc8fEQurWDTWVoJdTveoGNmwj76V3nEai5ZyaETR5", - "GqE+O8plL04Fm9Mvc2p+sR8dm+dfi8D33Jls+uS8H+MWaQkbhEW+SIbCBsq+7ou36UB+N44CzkNfsNU7", - "WLE83xDsTIWcucCVXnkMTEau3U89Sz0cGpagJJgoVmEVmRQ+jLdY+Sm4JuG0vffBF4TGnswoeMrK9Bil", - "Pg8sYlzF/nt7vhXvHtaUXMO0mx3sb8hF35wk5YWK+9V6typx7bxvz/mU7ECeCsv1Xbgtk/atwqPwAOTX", - "+09unliGldBCMZqvXH1eJzA8uJUAAsXI0v4HTw+iRsQMYs/IhW5BtG5qexFdE0R5ns2JFM68f2vspmqx", - "mxaRgtrAjNC6Tztef71aFFxcuvZziKAOAhgSYpCoOKBUVnQpisj6hl1okVq49pyubHJGiyJc8Dr4pqYf", - "CNR2wLJbECU6vkywmLg7tyVudC3NiFsPb0s54pO9USqSan+9LUH5ArQk2f05td7QRAfK50sQ5+ODGMY1", - "Puw7rl2yc6XcqSsD3cUJ9Wgdw8D1rMcY/VIqo93Frxmv29hGhH+KSSLUBxgFttEeMDS49UFL2CUbV1GT", - "HXhXGysghCV0bwkMu/fBd1D/uPcBfuH/WONQj5spS8V8NFxLBty6Nz5UV+wKjP7Vnfzww868UQVm31Y6", - "FF9OzOp3v82soaLtTcf+pxpob2mIvFOXKC40Ujf6TrZ8bwiY0X1ZR7wDRv5zI+MwZVRxRIU32wlzVx2S", - "TZkioY+8b2dRuCSrd4OD/e/eDQJi1bWBQakA/56plPAifb09HeQ4DDMNjfs7B46ZcrTQEsfQcsGkYIQV", - "GsapSwKnlgnYAgCcM4pZwA6E/22E04yeUTF6bvc5egsDDBIwjBrrpmAoFZ9xQQuY044P3TCw5nAh4xrF", - "Tl6walzUEgYbEPowANy3U/J8lUtBKIc3oPPLjGMY6aa9vXYLG710CxtsjFXaRp6RmWFmpI1idNGkEEFT", - "n3Bh7/dwcy7nM5xDx/h/PbuiF0O7JsWD/e82ve7QsYGIjuRgkPLj5AjKfW7VAQwhnjCzZA7ZffPzmugE", - "rd2Fg8ACsBuA6tCdIDp7XAZl52GqEG3c+XvDrfU3sL45DvFKJTNXZHjC7Idh/smqce9QorjovUJHBLpc", - "u9JFQF1icNx2APQGDgScwYVA9/Md8rM0rO5j3XgI93MqVcYnxYpkhXR10X84OzshmRSCZdg+H/uNSKit", - "5Qivq4elG+fFCHtPM0M0XTAnSRrpexWRXFZWyMMP9Pid8KeK2UF4m+rKwokTIBOZr3pZaZyGaqeotYsu", - "WGLJEayLex9cO4iP6w3QrjvqFmGXobvE3TQQusrVSccJFj0TU3lHLcvNPidrzHaJL9ac/J4ror/+9H1b", - "lq8FCfx+1uECNFrx+NAT0NSWmODDOdVEQG8BsmLmbqFTHIHQ6WmDkdoLhuV/cO8bHGCueEMr7CD0ut6A", - "eMY1/d+IfGf2xbuDfIa9N3tlQbnYsRjGWRs4XwteRXFRVBsyZcuoo7nbwD2N296CesWfhPF8Y4+1WLVd", - "UEDUp+NWserzWyA73ZK++rgAZIFfQWAANsGBgDIMML9ihE2nLDNerIVGlzgC1WTJisK97y3w0HOUUZec", - "Pq8WVGiMgQbhFFzIV5x2E+brxhX2jkDpWX+jMKARLlZ9ry4IF9owmrdK20R1QXurMIRmHzfG0n06hp/q", - "2pUPQ15HowduXb1gfaUAVO106OmKzYe8Cdi4bFTUJosVofV0CQkdj2G0mJk911ph70PdpmGLrJJmf4Vt", - "lXLf8CQketzliOy49m5oTgIXpBJYc1U3upqG0HW/S7T527E0ZLnWx1uDf0Mo9wYwfz4kb/XLSJP5FjAS", - "aB4Ug/arvXvfzB9rvPxUFllWCThjhaUuoD8/N90Kxq57XAKA1zSEeWx0zePC1cOE/LuTFeoqXVGBHn0o", - "i7UtEjWQcOi2CoXIkYoR2sXddcRwQ8xc4yD1rV3LVz35D780tqbHazIUl+1X++9lulglBAfcmcvy2S9J", - "FN5yd25GiMgQbHmdO4HiRffs01cg6mS06QbcAur34fxfwDzv17oJ4fVWcHJZDv5TL8M2PAGpktJd4O19", - "cHXWd5ClttIVw7A3n6fbqb3q8CcwEBekdzdFN68GLV3jnmPQZhTL5GIRGnSCczKDWFrwjLh6ibVlZBna", - "DHBBLlzPjgvQmtC113wJYylcR4Kh5bAl4YZMudJmTJ6KFZpa8LW4bH80jHcGAk2tQnuM6wmOXxSnPjcp", - "WMP6ts33XYaWHdsIEiRnBlpKhyP2Btvtbv6eZiYWKHoNRh154lYP7YZ5d6vbR5qRe5rr+fDxc93Qv2on", - "lu+4SuT0ZqxLd8Tw04uYUUVYDy2EiJ7zMpgUQhuQXZB1k23THWK3l8vXgrLJ/jR3weR515FyO5vkcnek", - "LBgrRzrq2beJ5TWb/H1N/K+5s22q5UPoSqOr4brUYxZLeEKmvrybaLiBr35RjLgxSrUJGXwmcfsUr60i", - "h66KX9SCdE36ZKU5GWxgjb50CTRvOSuwqRVTvnXuGv6ILwbh++bOv9Hvt18wBr6Ei7rVXAAPCZb3y+4d", - "p8ndiQTzy2/YXDpaQ4cH1kdi5bD6S51AKqv8jeR0ukYx4DPxejrdyrly92DpWs8BiW00nfsb9LGLrVPq", - "MlaAqSa+OeYGgD+jRYExid5UYyQpnLPNlyy9FHJpf1jdU4zMoGCKG37ceypiw6GIG73abor+S71ghubU", - "0Fu90d1WsX+IK701Gj6tzJwJg62cXQMoiw0+YLLPdPDJOInhxkbCDC7TVkacitcHnsRY49Jdk4JxdGqD", - "L40csFKvGNQtgPsEUiFJ/xd3G6t2xxCfxxW67SrMjRCrHiD0osIoq3smp0lYor/yTevUYaKU1lI7NXTA", - "050l1D8w5fHuOwSRNy5DoEEWrF6a0MySjYLlWIEQ06McRRk1I588uoAHlIs6LcdRGaZGhcxoAQSOFvpz", - "U7Ur1thNlfI1QQjQGj7r5HEXHX5zVWCdFb43eBuKqkV9BPrI1c/SV/0MyZehFFZkjHuwf/gZe2ohivUi", - "5glTvqXBcyY4kk6XpZ+2o2OgnGN5rnc+YNSQaBkqQRWFXKLjwoHFbV3x2dwQIZcuTO/wdhmMv0hUQOYZ", - "evOsFA6rw/wxyEufSeiV7PIv8MLteGmdr5CG8SNobLpNgFNe4VTpbhPJOLn+62KHRMPw1xBy6nbSdx2d", - "bBT1ZL++VcON1Y0xTd2SOpNDN7tuO0zyxSe1dFlbYey6gNptG0w+kTlF3ga78yExq5JnEGHo2oCAwFwq", - "OVNM6yH0CcEKOMB9ppQXlWIbOYznK5qJvOG1s+D2o0ONaKbY5puyt6CrER+pqj949Ce6cqaUSnwVqSc/", - "0dVfGCvfuIb4X5d6huHdToypc5QjiTnyw0cMSlWC7JFLxkrvl6/DvMnr0lc4gnQ5yoUmlKDfPZZJgz8j", - "5YzvQeSORA/KXrSy1pq4rmPP16O2rExZmVGpZF5l6wR9Syxfw8sn/t07wRygMtXebyWb7ZozPHTflmL2", - "pdKND7ZMNwbpzyXS+uYUD+7fv/mL9oqJmZmHEj1/ilsS5TzHRrSWylLiQDByn2D2uFvp4c2v9ISuIKsU", - "+iFR5RrJPLj/8DbcCLoqS6nsQf3Eck7J2ap0HjNAMYIY5YXJSUiKrtsLxqFgDw6e3E7rKl+lATklkA4p", - "yYKKFZnai+3Kwbl4CTNX0piCuaJxfyjJA7OxLaAXUhuiWIY56qHAHewX5YEoJ5sDcKrSh1XVjhAmNFao", - "w0wJkN7dKdsv72mS8xnT2Ka/dcbkWciRh6Cxk5+/Bzj/ePLie+JQyQ5aFlSIdNDWOoHHzKvFRFBe6L1S", - "sSvOlp4scYVl/Ty1J0j9vRgEEFVXnppXqhgcDfYGkRGqTayOmxFRnRZfHlMCO4BUlG65ix/lxJtJQUb7", - "e8UUt+hX99EbtpomjBu1HnVi0Kcnx83GY7GJTC4WlUBxE8popNp3Nxy4iQkcNvwU1kSgB3dv209suWS3", - "Ye+KkoVfUWcycDomCrpgknyYBfhEneHvIBiaof0mJ6FuWTyHS8r/+OvH/xcAAP//hNj5AroAAQA=", + "H4sIAAAAAAAC/+y96XIcN7Yg/CqIul+E7PiqihSpxVL/GbUWm27Z4ohUeyZaDhKViaqCmQVkA0iWqhWM", + "uA8xbzJzI+bH3F/zAr5vNIFzACQyE1kLJVK0+vYPN1WZieXg4OzLx0EmF6UUTBg9ePpxoLM5W1D485nW", + "fCZYfkr1hf13znSmeGm4FIOnjaeEa0KJsX9RTbix/1YsY/yS5WSyImbOyC9SXTA1HgwHpZIlU4YzmCWT", + "iwUVOfzNDVvAH/+fYtPB08G/7NWL23Mr23uOHwyuhgOzKtng6YAqRVf237/Jif3a/ayN4mLmfj8rFZeK", + "m1X0AheGzZjyb+Cvic8FXaQfrB9TG2qqjdux8DvBN+2OqL7oX0hV8dw+mEq1oGbwFH8Ytl+8Gg4U+3vF", + "FcsHT//mX7LAcXsJa4u20IJSBJJ4VcP6vH4N88rJbywzdoHPLikv6KRgP8rJCTPGLqeDOSdczApGND4n", + "ckoo+VFOiB1NJxBkLnmGfzbH+WXOBJnxSyaGpOALbgDPLmnBc/vfimlipP1NM+IGGZM3oliRSts1kiU3", + "c4JAg8nt3AEFO8BvI1vOprQqTHddp3NG3ENcB9FzuRRuMaTSTJGlXXvODFMLLmD+OdceJGMcPhozPUX4", + "Zc9IWRheuom4qCey+KimNGMwKMu5sVvHEd36p7TQbNgFrpkzZRdNi0Iuif20vVBCp8a+M2fkNzkhc6rJ", + "hDFBdDVZcGNYPia/yKrICV+UxYrkrGD4WVEQ9oFrHJDqC02mUuHQv8nJkFCRWwIiFyUv7DvcjN+LGtEn", + "UhaMCtjRJS268DlembkUhH0oFdOaSwD+hBH7dkUNyy2MpMpxg/4cGOykeXRhXeFshl3UuGCr7hqOciYM", + "n3Km3CAB5YdkUWlj11MJ/vcKEdEd2m/uIiTnsReDqlniLjwTK8I+GEUJVbNqYSmMx7dJuRrbD/X4RC7Y", + "Md6t1TffksweQ6VZbt/MFKOG4Vbd/VtFa6iveE1ZdkAhvliwnFPDihVRzA5FKGw1Z1MuuP1gaAkBTG+n", + "HAJMZGXciqgyPKsKqsI59OCDriaefK6juglCdeK+DFd95xFO3eeXXHN3yXYc4a/2S15YAtym4hbH3Mq2", + "pLwnNShaBLiajOwThDjinAcreV4pxYQpVkRaUkn9uIDEEbHUY3L+w7OTH16+OHt19Prl2fGz0x/OURDI", + "uWKZkWpFSmrm5P8n5+8He/8C/3s/OCe0LJnIWY5HyES1sPub8oKd2fcHw0HOlf8TfnZMa071nOVn9Zu/", + "Ju5I37l0aaiDQLT76GIih6CaHL3wVwa2bQnHnwu7fjUmP0simLbkRBtVZaZSTJNvgEPoIcl5ZqeiijP9", + "LaGKEV2VpVSmvXW3+KEVHg4P7KYLSc1gCHi97SYj1IlvZkDGYYp7Ggkso0nhyLn75vwpocWSrjS8NCbn", + "QNeBnp4/RfSArx3peneEvBwA6jiAIt8U/IIR6oFGaJ6PpPh2TM6XbJIaZskmNdcCrFtQQWfMErUhmVSG", + "CGmQgbpZkC0BHo/J+ZznObMLFOySKRj6T21cdqTRrhSZjH0RgAMCrJ1d0KJJa/xp1QDFmQZAdBxcBsPB", + "kk02nlkaI70QVOMJCs9ck58ABAo5IzdAEenC8q2ExMQMTYhdP1A9j288cBly1CEBmjhuVdAJK0g2p2LG", + "hrgMOzJZ8sL/PCan9meukY9IUR9+YLtM6EpZzkJRQAvCQXNSez+qEtgxNaxB3msYwpJ2k9H9BFvrFykZ", + "tiP+tYizI1C4vGjOIZ7FJoJt0SHB1F9zbTyFApLbjxhdJPDi+/U2ftrghD27rqdIbdBd+GNq5s/nLLt4", + "y7QTl1vyPa104jK8qP9lYbCcr7woYOYW4b4R0nzr6HRSWOKirHqkc3iEGLmkGnUIi3lTLnKcxZP45MD6", + "DKdNqiQo8sxZWKhjJVJZujVOCi3AzJIrhUHCQqeyEnlyTVpWKtsocURHcoIftI8UgeZWFIaN9zx0B7bh", + "yF9xkdcnvhX+9SBMQvXq7sNSvViQoFrLjFODJNnu5oyJy0uqBg4x+gUIb1/onId7QBSzWgWI2JRoVGad", + "Vgz07gPLKsM22T36jQqBskePPYzTdCf6JHUsL5WSqruf75lgimeE2cdEMV1KoVnKQpMnUP2H09NjgmYE", + "Yt8I4nsYiBxZVpoVVY76Fl6KVSFpTrRErA4AxNU2YGuVRFgaF2jw4FKM34vndrKH+4eB64AoAJobNXRC", + "NbNPJpVeWe7ECCzUL8oxLykM5YJQcu8tM2o1emb12Hv46pxR0Avt8rjIeUYN007TXc55NieGL1BVtEfB", + "tCEZFVZoVMwobpXeV9KqzF4scQNyDYKLRRNqhWPPy+9px/fsu1nBmTDABSXRcsGsYjgjilEtBdAREKfY", + "B7w8nBZkQrMLOZ0ixwyWIS9Kds1SC6Y1naVwr4VccO71+ynMelXQBROZ/CtT2hkq2Ae6KJE2IooP/rus", + "lOdTlqbMpTKX/oPB4Xh/NGGG3h8MB4lfRw8fjWYPHj+6zw7zx6OcK7PymvAWd6k5V+KF/mctYPgXW2M6", + "wSMFmx/RGEmL4s108PRv62nfiReK7FdXwzaPpJnhl0G0X8MmUW7ThvgvrEzm7SpJzoGKf4rc2Qcgw/EF", + "04Yuyhi/rJA2sk9SY4Khh52568HyM5pgxEdTZwEoGExjGVz4wsmbXMOOwgqIZYR4B+319PfPfqqNVCiC", + "eqQMslHzZqxdOU8A4t27oxcetj+CEXWD/XVb068VMIPltyrz9Dmchs3LKZ4tvjreclNtDm8X7A+9njYy", + "CQdk+/XqV8TjPxcyuyi4Nv0y6hLYnHZUXTGgdWA5ZDnJmAJ6Cx4ClGSlpb66ZBmf8swj51ZiQryel8Ko", + "VUpC6L7UkTvXm9pxP2db2dvD2z10qHUC9dCxZb2HhLxw1+NITGXiDompJHQiK3st7N2w3G3C8FLVrBGv", + "v71N7kGXyes5XVBxllnBS6bk5li0PYGXiX85Mvj4BSi2kJcsJ7SQYoaGdq+hJyTgFoDaa+kBzWuqzVsQ", + "BFl+tKAzlobRSyGr2TwWIsCoQCNeW3KWMWLkDLeY8+mUKfsMTxBMqfZrQslcajNSrKCGXzLy7u1rz7nt", + "zRwptxzC7XrG5FRaWQONQ2gjeft6aH+yQoWghpH3g49WZLna+yhFMMjpajrlH5i+ej9A4tU8K/tBEy1V", + "kaRCbpiGBL7Br9E6CpgqGqnnKH5ihlrpC3hVnoNBlxbHzfvW5RINC7aacKOoWpGFG8xDf0x+kgpE7LJg", + "H2JTm5O7FtKiNejElRUnyTkdT8bZuaVB9YFbwF4wMGpHMkqpJOzj6eCkVNww8krx2dyqQJVmaswWlBd2", + "1auJYuK/TJxaKNXMv+GEnBN4gZyY//t/LlkRwbUBp2PnXnsO1pMuTYodigv6gS+sSnN/f384WHCB/9rv", + "ynStMwuD9BzWSWQRSR+WURXr+TYwNq9uAbdAtVBk9hjQR1gCnYG/Hf5zKUZTyvGN8EdplUn7x98rVsEf", + "VGVzfhn9ibZRHH4UJIQBbppVDJ9X9mBG8WxJ7S7soe8IUNROa+P4LPIJOfUHbWGfRRBok0LPlN2y+o7U", + "SNVLAN1DoIDBQjt0clQQluxdqjSYRpF427eQ0rGcWKVaIzsRLLMagVqlSFOLdJ+l5Kl7zz3fOHpxL1Lh", + "QCjxSlObxcT+wTF5xnOrW+JK/ScpduRVQ8f+PFuaKrkIW0/aGnsu8CnVF/qkWiyoWqU824uy4FPOclI4", + "uQi9mx7qY/IcVU9Ub+FhbdO2P/lDYtQKuVRfdFk1fLW1VQXiC9yCtzDo9ZJ4/V8rhnuOqCe43QdPH1ot", + "seYAfTT1ajgAn+vZZAVxCSh6noGrwyH6r/6vMy4a1CWQB0c5fu0ogW4tH2tSeT+t/n4yq3rFC8OUZTd+", + "sKFnPK+P/vKy5jtJB6qcTjVrLnQ/tdAaVB93iErQWxL3vh3FNvlddhWdWvtWvGWmUgJdMBbDUByknnpy", + "J6HCFnbRAqKomTZS9yNwnxUaUH/bO4VK+jXvktNKn0sx5bNKUR/h0VwP16+40uZtJdaJ4qgiW6bHUe60", + "tG5qP6yNVG4+oiqha39NiHkAsYmSKVuSKbVUUw+Jc9kJKUYQpmFF4SxeL/ADIlXQ7IIbZ2LZMWGL0ljq", + "a98ycwYOvqrIxT1DJqzXdQ8k/yWYufKtFBBYhVFU6ClT5NnxEfifvRsjbWvXyA1fy4ymY2teBO4BrMky", + "HnspYC738Xijlt2epb27YXzAa7Dkr1Rx72poI8iZWcolTbChN4KNlnRFLt3H6FyzcFtIbcBWLe19ZGiC", + "BM+05VxWwCkLmoGrFXnk+Ucr316dOy2HKwyL8dLDHHz5TjCgxMcCBocK9eZvcrqUiTXRQks/ad7x6QZB", + "hbnllwU1VukZBcMBBukAZ3eDTFZh0X2IBh9t1tOdcb0GtP9yi/N6VuWciaZjwplInOKgk+Jpaxi9jkut", + "o1Bt9OnwsJ9oWVoYwyn7QyF2yxCvY0IUEMeYvMSGV39hrHxbCZGM8jsKpvNldHERBmRBV+SCsdISJeHl", + "t7S0s+jM0z3QWmbvEcBR2H8bdIc1q/VuiVi0r+2SQZNcOrw+Mo62ofA8Z+QcH1nuxM6J3Yozo8aBZnh9", + "7CQA75m0/xXsg3EeeSTS55ZXnw/JeRMI5+SndyenVvM9h8CrHkRvoXMLkAFqfTBKYXnwzR1552pLf3WO", + "zPUXq+V6Swx/677iL+bSBaWF5Zs5ivPIbueIfctmlm0rliP97UKS5rliWu8Y7+zob/qmyalZUsXWXMNN", + "VOuXcHNQrgvhDmfBTqp3E4c/KWLaMQAPqjhq2gNiOMgwXg5WOIig0LP61GmdsKxS3KyCn7ZFAbd12K3z", + "1J0wU5XPtObaUGFQ+Ey5uGMhT06sbOfVZZC77CgkDNOl1s5A9hJ84HSLIMh+p/+XEtS6W0jCE8S5573m", + "8hMG6r+zmzj7N1fk5IdnBw8f4bXX1WJINP8HBBVOVoZpFMhypu3ySOEW5Z3nXQNHy5gJs4GvEcnPoA6v", + "Hc8kCqGDp4PDh5P9B0/uZwePJ/uHh4f5/enkwcNptv/4uyf0/kFG9x9N7uePHuznBw8fPXn83f7ku/3H", + "OXu4/yB/vH/whO3bgfg/2ODp/QcHD8BZibMVcjbjYhZP9ehw8vgge3Q4efLg4ME0v384eXL4eH86ebS/", + "/+jJ/nf72SG9//Dx/cfZ9JDmDx4cPDp8OLn/3ePsEf3uycP9x0/qqQ4eX3V1fg+R4yS1tb9G0qNXhBy/", + "jiOe/TjAz0GadAZ+Z9xvW6OAhlMdlCJ0PEaTjMmRILLImSLOVay9cd+NBfNaDvBbpdE38D5shxy9eD9A", + "u5DXjt0ohIdoA4qrAF3t3JlcRrqoZns6Y4KNLPXawwDz0dGL856IOocyWyq+uPZXvGAnJcs26sA4+LB5", + "TJtvU839UyZY+wwNaq1TSaWOXAM9nG+0jRigODvQ1w4iM6fCud6a7muqG4OCX8xFQlIf9l9fY3IaSRef", + "jnw9Bs1GcMd2RxKOukvgnApGvdRFkfI6WuUWHdHhtKTY8ibLejw0ZdQjBndgysw+p4kVNkltPGZyDKAz", + "H7uWMdak0YONDhi7GjfesF/YbQL4F27mtXNlK1B7JTwDcjbpAf3QialDkrOSiRxSrgRoeCjOfOVns63s", + "GR1Hjyumc6qx1Xrd8XZ8ZpW4EHIpIPyikDRHfQwjWJJmARzsLa4GsnucnnZtwQMEjQbsemWJGxIabkVA", + "uAX21n/4zfPCAMQ0V8PTAjGbEhV95lnKMD5KZ5uQzevO1KWVO17BUCEMBxDNchL3mv2NfXBBmUGuj4M/", + "bwsH6osZ7sPNoEU8UbhunxlXIvL9qViD6bFNwtF26OL578pzPxchXEv0FMtPNmlubVai4bOaY9HcCsVO", + "p4vCxKizqpL31f7+waNgD3bSWaUt5ncMzUa6ARNzoTAV7oEToO7pprsj5emmkYV3B0tsMAxfDQdFBKAd", + "bS234CppnXpRa8hh6w1DSHNNSeyQ2QUzR29+lJN34PtNpiZqZkJO+JBoK2XLS6aI/9o7GyB5C2yWekxe", + "WSGHLcG/OLTqELvkstJniKvnIS7Nk77Uif7TR616u19zoJ/pIs4UTeclN8C9k+82DnkKWYsPkx5xxaaK", + "6flZCIBYa8OPwumdxu++x9AL3M09jUEYtWMUEA6zDrV2obbaO6Hgn+DgpNkcsgMueV5RjOQgS5hlxgRT", + "aNeXZEHFyg/ictBLRTPDM1r0+kF3B2J/xYhdo4q3xrkl1WcumrSnNANe0WDicC/Xd8RedCOdk6Ph93AE", + "374MUQP2sO7x/B6Zclbk7tuhl1zqsFdwO2/lDOE9sc+uyEVUBqOJdOvIWhyP2kffHI5KVeNoInA05NJ4", + "ALqVprP8toxRNvNqMREQzrgRs9Khtan8vzqKGf8Kk6yDlKXy/cUtTpgAN24g+HiLNaGanO/p6Ntzwi7B", + "CgMVA4x0mcJeTI7etA8tMN1VHJPnfkxMcJ4xEz9H2xv4+uzF9hfY/7uQM41xDYIxl/RVFjzjplj5aScM", + "uRJ41u2j1TBsJKMuHCa8a8eQAuPUvjES1tOYeupR5jc5+RaUN/u6feWetush4LW0lzXF2mS5UepLHM0b", + "77vctiZCahCfSeo9Mf1cClOdjGxCZY9Uov7BSmrjzbyshaiyXFc6Yf3WI7U9LAPCTet/JTX2PlAkaCU1", + "5ILbE53uBIMQgVsUP8oJZG4UxS8hyMDxaqovCjnDh/G1XrvqU6ovXstZHxU7dZeAZPNKXDghDcI9wp1V", + "Ui5IzpAj5/jQpfrZJcFtpZeS5/bjHDfdZJcpPLY76Tqt7CICErmljclPdBUS/RZVYXgJ2XOCoSWefTBJ", + "V7CnZWtR9RSdfbthYU0l7TbWYaId/ljJmWJarz2D0r3UfwQgLPvXUK4GEHnJOQHkz7P54S68yS9wvVDH", + "SMlUxoQJLNKdtyWM91ztoRB7jlu7dhS9r5mFJxYWuOnYtlFsTuEC9Gs2jQMKqo0LFr6ebhMnEO6sPtzA", + "gW9WNZw//VN1jWZ5tet8c1Mi9Fp8c6EHa7Pz1mAicoFtcBHfXIeNLmRrDcFwkn1SX/ZqFYo7zNQh+W5c", + "K95GWWmfR+F1US9b4Kw9tzPNWMpKReswWq7j9dr3fX5/VIBju7VvRv2lX/2nIn8nnuYTvjrLQsLMth83", + "IspuVhvdOo97w+3y4yQvV5yjnSzeU4dbRFVujKwzTZrm9W1yJj49D809OPz9f5D/+Nff/+33f//9f/3+", + "b//xr7//79///ff/Geu6YHWJ8wfcLGfZIh88HXx0/7wCh34lLs7Qwn5o92QUzcwZrXIufYbBlBfMBYbs", + "oXq7p6d7v8mJxgCF+weHYxgyPuTjn7+3/yz14OnBg+FgqujC0pjB/dH9/cFwANqxPpPq7JLnTFr2Db8M", + "hgNZmbIyWByMfTBMuEoC49IFO8JW3FvddeFMYWV7aXC5Kmad8ZSUZu14rjQd1sQ6q227g4KL6kOE0RCH", + "PXKgdmaBbsGDGHM2qPIhNXPbQqYbjHAxgmyyT/lX62iuraxadS5bD9Q6Ae+orYkZ0Stt2KLOo3XftupU", + "QY5bJmeCa9Z1GLiXndEQIm0KuWRqlFHNQiCOm8IvyiVNvMcDfT8YkveDJRe5XGr8R07Vkgv8W5ZMTHRu", + "/8FMNiYnYSq5KKnhoTjp9/KeJueqEqDYf//mzcn5n4iqBDmHiGFZkJxrA8lmEKI/Y8au1uWelVJDqbKw", + "SMu9n2nvUaEFsTsaNvZB3g/QiKLeD3y4i6uxiiZsL21CkbRSQZo51eT9oOk/8eO9H9SwX0htihXaaS4Y", + "MUybvZxNqpmrvaYJo5pDlTNnXvFJiRiPzTOSywyqW0L9gKJo7CypzfUZRu0PZ9sXShuSTJY8dpmet8tl", + "je1o56F4ZrfU2qn7V50jbyk+ywl31j60buaSaXHPkAU1GWbN08xUtAgjdULNTrFoJ9jCdLsCG+CRLPIo", + "q6tZtbVdAC9UcfVGx/fiqLFAK80tkLkN6+gPKLqzKqnWXgPpq5KQFZU2LFGQCGUH8hyfo7nL3UJfVKlO", + "AXUmZjcYOXoREk+c0dhZQtA5Sk1404Pfkpy8KpAc2KVhWAwYojF/SapooxbbfJELi5b+i7Cipr9mK4uA", + "k0O6RucE0UtJJOnK3KdeLcZa3JDBpb2P2Aek+VpJQ8LHbEwmbCoVqxNBokSg8W7K5Oes530TtWkwf/Rs", + "sjrz+Ti7ZNI6xSKx1i0V3x10ZFBNjKwsnm4QmVFVE6ugpNj/ywN6+sya3RSUL1/u/KZK4nhStMuJb1tG", + "p63Cpyqtx/XUw2XaUFrdmWQ31oEBl5J0ZdUji+sn+Y7S8XeW0EAIWcv2OmzElHUxJTKxbpy5UkV64ndv", + "X8d+9Xp2wo1mxTTE6sqlKCTNt8mxqS204RSxtArsv+9UtrHUNt8JfmZIvXVsz0iHxSiPdU22UCoOGWEo", + "StsuH3LnzKhrDacRMdihokioHRJqAmg5NaN2SZGUf6Oe8C6V/4hp4jXqf8QVHrpmiUobwrolkGpigUcv", + "GzWY68gIUCbGPT6Crc28d4mVXNc2uyU99zP1ndQ6KrE1dXCY58LgwB3dSyfIM6snhdODCFBZYkLxn4h0", + "VqrWC3wmIDDpG5AOpc/IPvfcynkdhDSEKeoyX0PNwbYOZJf17Sa3RDeHveDCtQ9w1AsyLe5pkoUa9ZiA", + "zuMaY8DsyJtLppaKW4rnjaZggBZRaURfXyopfKU8ja/lzHkQAw1AZ6bXKXxpe7toOBWYkFFV8J5iwqZB", + "AnegEknkqrM9k5qVYpC2kjHQsMEUwgVm7eM4iWSAdYmin0YF1lwyP2nqEtV73K60prNIhxI4nZxi1AsT", + "TNMpoholbJQzuPY3ElodLNhigie7lQqBn7pxk1pEeRYBvMXDj4l71nFzrI0e3c5W1j/Wp2fhGqenbgYN", + "aLRbkd8IUo0w1KhQazL/9urXTuU5V3apyRo95a1R7nmf3cIbLOoymGPyDG0rVARaa8kPxLWtfN6O+4yb", + "yNYIVYyAgIyDBcNJsiVVYNYLwQwec4nm9jcqGFA5166g9jN0t9uo1mmHzyX5/vgdQXtzIKcvX/715cvx", + "IPgQvj9+N4LfuhbpVruinT12bi9j8hw36w0vrdJgFFzh7mXQMxwsKVgJFRW5XBAYONBk16hsKwPNtsSq", + "t+pvA1WaDrBEYSQMBDbS15loIIbukBu3E4sczRN2X5zxXNvVPTi8f5A/+i4bMfooHz14+OjR6Mlk+mjE", + "nkz3n0zYg+8yNklU0GqMEt3vzaGF6/Id4lE3Quy1KzLbT6I/B5296l3G622K3HaZ5K6mpDZPWg9BP3o/", + "9LDeQxSx1inC7X4ZBd9ywgelWaYYSMZyJKQZGVYUIypWUrC4ssHTweH4oI++Pv2bdxvayzZdlGzmuviM", + "6jYuVqHkOkug4DVLT7iFf/z8zKutj+FMzcSH1BTDTSTihM/Em57DenZ8BN3bIqif1bXR9ZLOZkyNKn7b", + "h9BdzM1D3PP9NJA7K1oD8IKx8sQZ/RNBMfZxcAr4zCO0n/kiVifGsmAqcsJEjqEhQTXxGR+heGNOV00D", + "VRjbknKwcYzJs7IsOHPhMRgaI+2HHAz25zld6TM5PVsydnEOmbzwTvN3+7KPfE+sENQ5QQ4ejOayUuSH", + "H57+9FNdy68jK0QjD54OFpKYikCKFESc5megMD8d3P/u6f4+1qNx9hrn9td2Bf6t/Sf2ra6w0Jikm+5M", + "MzbSrKQKY0eXclQwaCbl6zE7qFuJwI4FtJmxix4wk2/eDxYSXa+m8l7Xb8fkJbh5FowKTd4P2CVTKzue", + "r7rcQdR6/xFTBID2FBXyoPmYTvMIgNo8XFskDmMPm9BsjButeM29MNSwPnOYi/1RceWs7WOHksasaLCt", + "FpW3aGRI5qRLesG6yHWdIKftMxwb38Wx4RbqmMeN6xoOqLYkxR4C1PUZDgzT7hU5nRZcpCPH+yOoegVI", + "JFa1pchJk3WOP+TZuHjRhDFPnxX0H6v1eYTNgmhOYUHzS9zeEYhU7WBGcaM22TgLlSZTLriet1zFOydB", + "bXOKw7C/NefZZz79M9U8W6MdXtsy+uXiDj9Xba7PFhUYCRNNQPy1DrUJ2ZEtlUiFanbXsOBulhm8g307", + "S1Oz1PLH67rb0llWCcPFKTr5g1IYYeUVSsVQVszKPItYTzmjVarAxzvNFBSAdPmrDvGOXgxJSbVeSpX7", + "RygGuzqfVsjx9sVaDbGICYCBi22vUb3TuTHl4OoKmrShOxMSNTITycDhxE8ZXThHHH6pn+7tTX1EJZd7", + "3eKWmONCXlG1cClhkPI8GA4KnjFXhSHYNF5fHnbGXy6X45moxlLN9tw3em9WFqPD8f6YifHcLLDIPzdF", + "Y7WL0OeoFtjvj/fHIAXJkglacuxvNN53dUTgZPZoyfcuD/eydlngGSo2oY7kUQ6tu0yzfrBFGSzhAKMd", + "7O97qFpJ32KwFTQxg3vvN+fhQrzdMoG9OR8cXhPowmJ1EUpJIAp6umpXjHaeZoW5aaeLoaEzjcXsDAXd", + "pB7jpchLyV167sy1oO4M2EmktpBPgncPApf2vKrUB+xXXOR/DkXhjrHyy42BO91DLwHvV7ISdY04kIFD", + "18Jme/LPsi4sTphYx0noUra0DH6pJHQwb5zcK+4SFqUiC6kYef76yPfMQ2cKRPlpsqQQHwjSlN9OCilK", + "qRMnBQXEEkcFrObPMl99Nmi0CqEmwOK7BUrlfHEQV4XFPyWGzGFO+83jUaOwYnelPzcv7hAXiUF9cKRT", + "Ltjdw6m/0oKDQ5TG2HQdZGrhqfOqXtbj+97F9UFuJCpYZmQUhT2vQdlG2ZQvirXHt4af/xSIidVlaoxs", + "Fp/ZwO52GKcXGaGg2rZSxCusvvZJR75DQ6GrYWOsFV0UzbHacvEmBGkfxFvox3nJ0oJHV05YexrPsoxp", + "Hfp0JrohJIYMoe9CGoIbuwc+9zclE8+Oj3ydgaKQS5Ssz33z8T0nSboDPSclzS7sYb8X/cetmanKEfX1", + "efvJzgm9ZMmSwDdDeJJTJZlmDFZLu+kloncLKR8kMuhayADx9ks2oWXpzRW5VZGmVVHUpWCMqxRu5cq7", + "R0re1SE/PaWpsGKwYsjkOBSMtTtckWklMryJ0CBtA3pbhEhhdm/l534cbHC+vY++WtTV3kfvNLlaR5Ia", + "zLDZ3Ngq4NzCzpVfdCpcVI+qVpydNXoXFadbo8tq8YkJI+dP/4Rt6vXrDTLTdN213Smm19JaRdKKRr22", + "Wl9rVWqzXzqTgC/UZpEzVGlDU9+O+t265TTaePUWb+tH1ZDytTuW1h06/hNDr7EB/QnIWVf2a5sPyDuN", + "HWDsa15op3k+QmayJucPyWho7sEmmN82pdAX1DKOVGoMmVBdV1+eKLnUjeS362N8vcfdcdy3surh/JBa", + "hBXhboTVNzpZdw/5Rzlx9W0W3HTQ8yY1jjULAuN6ZSU85J0uJ86Kai78NKrzpgHaD+4f3LyMcBooakj+", + "Y4bOIEfQdZP3SYLNF5IpglxDkmqxInnFWh3nM5rNPfKFoeA+SEkKifkGtykewQPiW1o0KQHimAsGg54P", + "UnXuCJblgrTBWPbBvmyN4X5sZkwydyk7lwpV+y2uFui1X/Z+ZdES1l2vB+lKBjteiJDbaqkotrycW4Hy", + "5zenmE7i6ly69II6+dDMZTWb/+eF+qNcKECrDdcJsD/s244EpjSogLfk9sRNHdDJE9esURGx3yzPTDb/", + "vpAT2qhrBglyN8tF+qojbiHQDNNX7tQXe/TJ33B7qFgl24r3yEXQjBxyppm6dJleic/1huN7A11/sBFt", + "nSU0A0D3LKd1fn/3nWLTZBL6cLqKdTdBIetmtSmtu91LAeOzoC8pFlAY37ZQ0mhM2o9FANXIGOqiwjFV", + "HUo+8KklYUB1gIy5fqDw4fjO0Bq4t6FGhQX8dghZt46dQrdaCAcXOdESAm+6aGgp7t5H+9+f6YKt1eZc", + "CYetdDk/4J1RrdqFKHqlAnzWJh0uxjHwKAtT6P8YILHhfKLk46hWe6h7kTwXvcVp6MEtAi2pkIaXwm50", + "AoARKuM7KAVBDdqtgVhPFdhuGK8Lwo8YFHJVV1/rAvIF/I6K3masDgnP/Ti9KWzl122EyxdIgiI6Fkqp", + "h8IjRvHZzDKY2yVa7wT7UGJFFojY67oTMNouLNiX/hgSLrKiylGecRXFsbWu5eByhv09UEp2xVzCIAu6", + "CmF0zo5As4uZkpXIx+RnGXra6ZDR4srlkW9WzHzbtDEEzOoXmb4oRtyKNs99seo202nJNL/JyRaaIX4k", + "chKFzvfdx71JIbOLIiSRpG/mW7aQl/Zm/jm8fZsHciMSV72VlNZVlRZ/v1m6YpOYcr4q2beuJr4CiETV", + "lQIct3T++LtJs4yVUK+HCaM4c3ookBU3yV0jKnZRYbWuBZC98xEIdr3fXwavbu6ir0UuUH/WIJjViGbS", + "IDyjojhw++8SKiCNAq2tmW9Wd3PyewA0ySXEv7kO/GHLurnD9VIHOrUDqsWNB/qljl0U9La6jNr514CU", + "f3ArQPOor2ERSA4a6iKsRyDNTFwBpMecCprAcV1m4w/OIv1OXK5Nj3VSsCXxsBlfz4DrJwpZxVQHxoim", + "1oODvgo3vs+9X4IPXsHvQ+jbFyaaa5A1SAL1FhwYmi7qjQhap0WsQ8+TUA7mj42cjapIPajZTAEChyqs", + "5ZpoetIY7jpI2lyQw1QwNofD9nlHOvTMC5L/HwSNm5vcBYlDn6y17PkU3vo6eDLsJaTgpGVFhDFnOq5O", + "pDuSzx0TC6lbN9RUggZm9aob2LCNvJfecRqJlnNqRtDZbIT67CiXvTgVbE6/zKn5xX50ZF58LQLfC2ey", + "6ZPzfoz7AiZsEBb5IhkKu4b7ui/epgP53TgKOA99uVvvYMXihkOwMxVy5gJXeuUxMBm5Hlf1LPVwaFiC", + "kmCiWIVVZFL4MN5i5afgmoTT9t4HX04bG5Gj4Ckr02OU+jywiHEVm07u+f7Te1iRcw3TbjSavykXfXOS", + "lBcqbtLs3arE9bC/PedTsu1+KizXt563TNr3x4/CA5Bf7z+5eWIZVkILxWi+ctWNncDw4FYCCBQjS/sf", + "PD2IGhEziD0j57oF0bqT83l0TRDleTYnUjjz/q2xm6rFblpECiorM0JJzhXLjFTu+uvVouDiwvVcRAR1", + "EMCQEINExQGlsqJLUUTWN2y9jNTC9aR1RaczWhThgtfBNzX9QKC2A5bdgijR8WWCxcQt6S1xo2tpRtxv", + "e1vKEZ/sjVKRVM/3bQnKF6AlyZbnqfWGFkRQj1aCOB8fxDCu8WHfcT3CnSvlTl0ZaKlPqEfrGAawXB+j", + "X0pltLv4NeN1G9uI8M8wSYT6AKPANtoDhq7OPmgJW8PjKmqyA+9qYwWEsITuLYFh9z7CTLpaXO19hF/4", + "P9Y41OMO4lIxHw3XkgFbCPHDs4OHj4ifx2OGnQyqK3YFRv/qTn74YWfeqH6176UeSlcnZvW732bWuiby", + "rzd+8Tpd47c0RN6pSxQXGqm728tmd3tA5qaAGd2XdcQ7YOQ/NzIOU0YVR1R4s4c2d9Uh2ZQpx8EDpwZo", + "AM9/PzjY/+79ICBWXRsYlArw75lKCS/S19vTQY7DMFMk8Y6DNw4cM+VooSWOoeWCScEIKzSMU5cETi0T", + "sAUAOGcUs4AdCP/bCKcZPadi9MLuc/QOBhgkYBh1k07BUCo+44IWMKcdH3qJYM3hQsY1ip28YNW4qKEO", + "tm/0YQC4b6fk+SqXglAOb0DfnFmoA79+b2/cwkav3MIGG2OVtpFnZGaYGWmjGF00KUTQ1Cdc2Ps93JzL", + "+Rzn0DH+X8+u6MXQrknxYP+7Ta87dGwgoiM5GKT8ODmCcp9bdQBDiCfMLJlDdt/xvyY6QWt34SCwAOyl", + "oDp0J4jOHpdB2XmYKkQbt7vfcGv9DaxvjkO8UsnMFRmeMPthmH+yatw7lCjOe6/QUwKt3V3pIqAuMThu", + "OwB6AwcCzuBCoPv5DvlZGlY3b288hPs5lSrjk2JFskK6uug/nJ4ek0wKwSAh03drkVBbyxFeVw9LN86L", + "EfaBZoZoumBOkjTSd3oiuayskIcf6PF74U8Vs4PwNtWVhRMnQCYyX/Wy0jgN1U5RaxddsMSSI1gX9z66", + "ZhpX6w3QrrfsFmGXoTfH3TQQusrVSccJFj0TU3lHLcvNLjFrzHaJL9ac/J4ror/+9H1Tm68FCfx+1uEC", + "tKnx+NAT0NSWmODDOdVEQG8BsmLmbqFTHIHQ6QiEkdoLhuV/cO8bHGCueEMr7CA0eN+AeAYapm6BfKf2", + "xbuDfIZ9MHtlQbnYsRjGaRs4XwteRXFRVBsyZcuojb/bwD2N296CesWfhPF8Y4+1WLVdUEDUp+NWserz", + "WyA73ZK++rgAZIFfQWAANsGBgDIMML9khE2nLDNerIU2oTgC1WTJisK97y3w0LGVUZecPq8WVGiMgQbh", + "FFzIl5x2E+brxhX2jkDpWX+jMKARLlZ9r84JF9owmrdK20R1QXurMIRmHzfG0n06hp/q2pUPQ15Ho4Nw", + "Xb1gfaUAVO106IiLzYe8Cdi4bFTUJosVofV0CQkdj2G0mJk911ph72PdpmGLrJJmf4VtlXLf8CQketzl", + "iOy49m5oTgIXpBJYc1U3esKG0HW/S7T527E0ZLnWx1uDf0Mo9wYwfz4kb/XLSJP5FjASaB4Ug/arvXvf", + "zB9rvPxUFllWCThjhaUuoD8/N90Kxq57XAKA1zSEeWx0zePC1cOE/LuTFeoqXVGBHn0oi7UtEjWQcOi2", + "CoXIkYoR2sXddcRwQ8xc4yD1rV3L1z35D780tqbHazIUl+1X++9lulglBAfcmcuy+yW5ZYoZWo7XYTNo", + "e/UxNKEFmA4ukiHRsrYvZrQonGHxQsglhH29e3f04u5c3BAwItjyOlcWpZ8uaqZvaNRoadMFvYWb2Xcl", + "/wLeA7/WTfdRbwUnl4ThP/UidsNRkap43QXe3kdXBn4HUW8rVTYMe/NpxJ3SsA5/An9zMYR3U7L0WtrS", + "9RU6MkgBMrlYhP6h4DvNINQXHDeunGNtuFmGLghckHPXUuQclDr0PDZfwlAP1zBhaAWAknBDplxpMybP", + "xAotQfha3FUgGsb7KoHkV6F7x/Xk2i+KU5+bFKzhzNumIy9DR5Ft5BySMwMdr8MRe3vydjd/TzMTyzu9", + "9qyOuHOrh3bDokWrGUmajXua67n10QvdUA9rH5tvCEvk9GaMX3fELtWLmFHBWg8thIie8zJYPEKXkl2Q", + "dZPp1R1it9XM14KyyfY5d8Eie9eRcjuT6XJ3pCwYK0c6aim4ieU1exB+TfyvubNtivlDZE2j6eK6zGgW", + "S3hCpr68m2i4ga9+UYy4MUq1CRl8onP7FK9t5gpNH7+ogeua9MlKczKY6Bpt8xJo3vKlYM8tpnxn3zX8", + "EV8MwvfNnX+jHXG/YAx8CRd1q7YbDwmW98vuHZ/O3QlU88tv2Fw6WkOHB9ZHYuWw+kudQCqr/I3kdLpG", + "MeAz8WY63cr3c/dg6TrjAYlt9MT7G7TZi61T6iJWgKkmvnfnBoA/p0WBIZPeVGMkKZwv0FdUBZuembPV", + "PcXIDOq5uOHHvaciNhyKuNGr7abov9QLZmhODf0C1ti4k+0f4kpvjYbPKjNnwmCnadefymKDj+fsMx18", + "Mk5iNLSRMINLBJYRp+L1gScx1rhs3KRgHJ3a4EsjB6zUKwZ1h+I+gVRI0v/F3caq3THEp5mFZsAKUzfE", + "qgcIvagwyuqWzmkSlmj/fNM6dZgopbXUTg0d8HRnCfUPTHm8dxFB5I3LEAeRBauXJjSzZKNgORZIxOwt", + "R1FGzcAsjy7goOWizhpyVIapUSEzWgCBo4X+3FTtkjV2U6V8TRChtIbPOnncBa/fXJFaZ4XvjS2Hmm9R", + "m4M+cvWz9EVJQ25oqNQVGeMe7B9+xpZfiGK9iHnMlO+48IIJjqTTFRFI29Exjs+xPNfaHzAKfKa+UFVR", + "yCU6LhxY3NYVn80NEXLpoggPb5fB+ItEBSTGoTfPSuGwOkxvg7T5mYRWzi49BC/cjpfW+QppGD+Cxqbb", + "BDjlFU6VboaRDOPrvy52SDQMfw0RsW4nfdfRyUZRy/jrWzXcWN0Q2NQtqRNNdLMpuMMkXxtTS5dUFsau", + "67vdtsHkE5lT5G2wOx8Ssyp5BgGQrksJCMylkjPFtB5CGxMs0APcZ0p5USm2kcN4vqKZyBteOwtuPzqU", + "sGaKbb4pewu6GvGRqvpjW3+iK2dKqcRXkRnzE139hbHyrevX/3WpZxh97sSYOoU6kpgjP3zEoFQlyB65", + "YKz0fvk6Cp28KX0BJsjmo1xoQgn63WOZNPgzUs74HkTuSPSg7EUra62J6zo0fj1qy8qUlRmVSuZVtk7Q", + "t8TyDbx87N+9E8wBCmft/Vay2a4pzUP3bSlmXyob+mDLbGiQ/lyer++d8eD+/Zu/aK+ZmJl5qCD0p7hj", + "Us5z7JNrqSwlDgQj9wkmt7uVHt78So/pCpJeoV0TVa7PzYP7D2/DjaCrspTKHtRPLOeUnK5K5zEDFCOI", + "UV6YnISc7br7YRwK9uDgye101vJFJJBTAumQkiyoWJGpvdiuWp2LlzBzJY0pmKtp94eSPDBZ3AJ6IbUh", + "imWYQh/q78F+UR6IUsY5AKcqfVhV7QhhQmMBPUzkAOndnbL98p4mOZ8xDRV422dMnocUfggaO/75e4Dz", + "j8cvvycOleygZUGFSAdtrRN4zLxaTATlhd4rFbvkbOnJEldYddBTe4LUf1sxyEtO65nEsXvr69EgWjvq", + "0yQ8eD6PLhFGSygVn0unSM3xNSgXYV+NNMHd7lB6lKE3Z9gTtg9cmR5SMmVJiiX2l7SomL9TsAV16ZG/", + "UsXg6WBvEBl222d41Iwy7HT189Q33B7IPutWuPlRTrzrAVDi7xVT3JL0unXmsNUnZdwo76oTgz47Pmr2", + "GozNznKxqASqcFA5J9WxvxEUkZjAUdifwpoItN3v7fSLXdbsNixyKln4FXUmA0d+ooYT1sUIs4DsVRf1", + "cBAM/Q9/k5NQqjCew9XhuPr16v8FAAD//24nmI2iBwEA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/pkg/api/openapi_types.gen.go b/pkg/api/openapi_types.gen.go index 7e10b218..6bb181f6 100644 --- a/pkg/api/openapi_types.gen.go +++ b/pkg/api/openapi_types.gen.go @@ -578,6 +578,16 @@ type SocketIOTaskLogUpdate struct { TaskId string `json:"task_id"` } +// Task progress, sent to a SocketIO room when progress of a task changes. +type SocketIOTaskProgressUpdate struct { + // UUID of the Task + Id string `json:"id"` + JobId string `json:"job_id"` + + // Indicates the percentage of the task that's been completed. + Progress int `json:"progress"` +} + // Subset of a Task, sent over SocketIO when a task changes. For new tasks, `previous_status` will be excluded. type SocketIOTaskUpdate struct { Activity string `json:"activity"` @@ -680,6 +690,12 @@ type TaskLogInfo struct { Url string `json:"url"` } +// TaskProgressUpdate is sent by a Worker to update the progress of a task it's executing. +type TaskProgressUpdate struct { + // Indicates the percentage of the task that's been completed. + Progress int `json:"progress"` +} + // TaskStatus defines model for TaskStatus. type TaskStatus string @@ -901,6 +917,9 @@ type WorkerStateChangedJSONBody WorkerStateChanged // TaskUpdateJSONBody defines parameters for TaskUpdate. type TaskUpdateJSONBody TaskUpdate +// TaskProgressUpdateJSONBody defines parameters for TaskProgressUpdate. +type TaskProgressUpdateJSONBody TaskProgressUpdate + // CheckBlenderExePathJSONRequestBody defines body for CheckBlenderExePath for application/json ContentType. type CheckBlenderExePathJSONRequestBody CheckBlenderExePathJSONBody @@ -964,6 +983,9 @@ type WorkerStateChangedJSONRequestBody WorkerStateChangedJSONBody // TaskUpdateJSONRequestBody defines body for TaskUpdate for application/json ContentType. type TaskUpdateJSONRequestBody TaskUpdateJSONBody +// TaskProgressUpdateJSONRequestBody defines body for TaskProgressUpdate for application/json ContentType. +type TaskProgressUpdateJSONRequestBody TaskProgressUpdateJSONBody + // Getter for additional properties for JobMetadata. Returns the specified // element and whether it was found func (a JobMetadata) Get(fieldName string) (value string, found bool) { diff --git a/web/app/src/manager-api/index.js b/web/app/src/manager-api/index.js index b25813cc..340942fd 100644 --- a/web/app/src/manager-api/index.js +++ b/web/app/src/manager-api/index.js @@ -61,11 +61,13 @@ import SocketIOSubscription from './model/SocketIOSubscription'; import SocketIOSubscriptionOperation from './model/SocketIOSubscriptionOperation'; import SocketIOSubscriptionType from './model/SocketIOSubscriptionType'; import SocketIOTaskLogUpdate from './model/SocketIOTaskLogUpdate'; +import SocketIOTaskProgressUpdate from './model/SocketIOTaskProgressUpdate'; import SocketIOTaskUpdate from './model/SocketIOTaskUpdate'; import SocketIOWorkerUpdate from './model/SocketIOWorkerUpdate'; import SubmittedJob from './model/SubmittedJob'; import Task from './model/Task'; import TaskLogInfo from './model/TaskLogInfo'; +import TaskProgressUpdate from './model/TaskProgressUpdate'; import TaskStatus from './model/TaskStatus'; import TaskStatusChange from './model/TaskStatusChange'; import TaskSummary from './model/TaskSummary'; @@ -420,6 +422,12 @@ export { */ SocketIOTaskLogUpdate, + /** + * The SocketIOTaskProgressUpdate model constructor. + * @property {module:model/SocketIOTaskProgressUpdate} + */ + SocketIOTaskProgressUpdate, + /** * The SocketIOTaskUpdate model constructor. * @property {module:model/SocketIOTaskUpdate} @@ -450,6 +458,12 @@ export { */ TaskLogInfo, + /** + * The TaskProgressUpdate model constructor. + * @property {module:model/TaskProgressUpdate} + */ + TaskProgressUpdate, + /** * The TaskStatus model constructor. * @property {module:model/TaskStatus} diff --git a/web/app/src/manager-api/manager/WorkerApi.js b/web/app/src/manager-api/manager/WorkerApi.js index 9c001e7f..0652666e 100644 --- a/web/app/src/manager-api/manager/WorkerApi.js +++ b/web/app/src/manager-api/manager/WorkerApi.js @@ -18,6 +18,7 @@ import Error from '../model/Error'; import MayKeepRunning from '../model/MayKeepRunning'; import RegisteredWorker from '../model/RegisteredWorker'; import SecurityError from '../model/SecurityError'; +import TaskProgressUpdate from '../model/TaskProgressUpdate'; import TaskUpdate from '../model/TaskUpdate'; import WorkerRegistration from '../model/WorkerRegistration'; import WorkerSignOn from '../model/WorkerSignOn'; @@ -310,6 +311,58 @@ export default class WorkerApi { } + /** + * Update the progress of the task. + * @param {String} taskId + * @param {module:model/TaskProgressUpdate} taskProgressUpdate Task progress information + * @return {Promise} a {@link https://www.promisejs.org/|Promise}, with an object containing HTTP response + */ + taskProgressUpdateWithHttpInfo(taskId, taskProgressUpdate) { + let postBody = taskProgressUpdate; + // verify the required parameter 'taskId' is set + if (taskId === undefined || taskId === null) { + throw new Error("Missing the required parameter 'taskId' when calling taskProgressUpdate"); + } + // verify the required parameter 'taskProgressUpdate' is set + if (taskProgressUpdate === undefined || taskProgressUpdate === null) { + throw new Error("Missing the required parameter 'taskProgressUpdate' when calling taskProgressUpdate"); + } + + let pathParams = { + 'task_id': taskId + }; + let queryParams = { + }; + let headerParams = { + }; + let formParams = { + }; + + let authNames = ['worker_auth']; + let contentTypes = ['application/json']; + let accepts = ['application/json']; + let returnType = null; + return this.apiClient.callApi( + '/api/v3/worker/task/{task_id}/progress', 'POST', + pathParams, queryParams, headerParams, formParams, postBody, + authNames, contentTypes, accepts, returnType, null + ); + } + + /** + * Update the progress of the task. + * @param {String} taskId + * @param {module:model/TaskProgressUpdate} taskProgressUpdate Task progress information + * @return {Promise} a {@link https://www.promisejs.org/|Promise} + */ + taskProgressUpdate(taskId, taskProgressUpdate) { + return this.taskProgressUpdateWithHttpInfo(taskId, taskProgressUpdate) + .then(function(response_and_data) { + return response_and_data.data; + }); + } + + /** * Update the task, typically to indicate progress, completion, or failure. * @param {String} taskId diff --git a/web/app/src/manager-api/manager/WorkerMgtApi.js b/web/app/src/manager-api/manager/WorkerMgtApi.js index 08c0ccaf..b86e7e5c 100644 --- a/web/app/src/manager-api/manager/WorkerMgtApi.js +++ b/web/app/src/manager-api/manager/WorkerMgtApi.js @@ -45,7 +45,7 @@ export default class WorkerMgtApi { /** * Create a new worker cluster. * @param {module:model/WorkerCluster} workerCluster The worker cluster. - * @return {Promise} a {@link https://www.promisejs.org/|Promise}, with an object containing HTTP response + * @return {Promise} a {@link https://www.promisejs.org/|Promise}, with an object containing data of type {@link module:model/WorkerCluster} and HTTP response */ createWorkerClusterWithHttpInfo(workerCluster) { let postBody = workerCluster; @@ -66,7 +66,7 @@ export default class WorkerMgtApi { let authNames = []; let contentTypes = ['application/json']; let accepts = ['application/json']; - let returnType = null; + let returnType = WorkerCluster; return this.apiClient.callApi( '/api/v3/worker-mgt/clusters', 'POST', pathParams, queryParams, headerParams, formParams, postBody, @@ -77,7 +77,7 @@ export default class WorkerMgtApi { /** * Create a new worker cluster. * @param {module:model/WorkerCluster} workerCluster The worker cluster. - * @return {Promise} a {@link https://www.promisejs.org/|Promise} + * @return {Promise} a {@link https://www.promisejs.org/|Promise}, with data of type {@link module:model/WorkerCluster} */ createWorkerCluster(workerCluster) { return this.createWorkerClusterWithHttpInfo(workerCluster) diff --git a/web/app/src/manager-api/model/SocketIOTaskProgressUpdate.js b/web/app/src/manager-api/model/SocketIOTaskProgressUpdate.js new file mode 100644 index 00000000..0000db05 --- /dev/null +++ b/web/app/src/manager-api/model/SocketIOTaskProgressUpdate.js @@ -0,0 +1,96 @@ +/** + * Flamenco manager + * Render Farm manager API + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + * + */ + +import ApiClient from '../ApiClient'; + +/** + * The SocketIOTaskProgressUpdate model module. + * @module model/SocketIOTaskProgressUpdate + * @version 0.0.0 + */ +class SocketIOTaskProgressUpdate { + /** + * Constructs a new SocketIOTaskProgressUpdate. + * Task progress, sent to a SocketIO room when progress of a task changes. + * @alias module:model/SocketIOTaskProgressUpdate + * @param id {String} UUID of the Task + * @param jobId {String} + * @param progress {Number} Indicates the percentage of the task that's been completed. + */ + constructor(id, jobId, progress) { + + SocketIOTaskProgressUpdate.initialize(this, id, jobId, progress); + } + + /** + * Initializes the fields of this object. + * This method is used by the constructors of any subclasses, in order to implement multiple inheritance (mix-ins). + * Only for internal use. + */ + static initialize(obj, id, jobId, progress) { + obj['id'] = id; + obj['job_id'] = jobId; + obj['progress'] = progress; + } + + /** + * Constructs a SocketIOTaskProgressUpdate from a plain JavaScript object, optionally creating a new instance. + * Copies all relevant properties from data to obj if supplied or a new instance if not. + * @param {Object} data The plain JavaScript object bearing properties of interest. + * @param {module:model/SocketIOTaskProgressUpdate} obj Optional instance to populate. + * @return {module:model/SocketIOTaskProgressUpdate} The populated SocketIOTaskProgressUpdate instance. + */ + static constructFromObject(data, obj) { + if (data) { + obj = obj || new SocketIOTaskProgressUpdate(); + + if (data.hasOwnProperty('id')) { + obj['id'] = ApiClient.convertToType(data['id'], 'String'); + } + if (data.hasOwnProperty('job_id')) { + obj['job_id'] = ApiClient.convertToType(data['job_id'], 'String'); + } + if (data.hasOwnProperty('progress')) { + obj['progress'] = ApiClient.convertToType(data['progress'], 'Number'); + } + } + return obj; + } + + +} + +/** + * UUID of the Task + * @member {String} id + */ +SocketIOTaskProgressUpdate.prototype['id'] = undefined; + +/** + * @member {String} job_id + */ +SocketIOTaskProgressUpdate.prototype['job_id'] = undefined; + +/** + * Indicates the percentage of the task that's been completed. + * @member {Number} progress + */ +SocketIOTaskProgressUpdate.prototype['progress'] = undefined; + + + + + + +export default SocketIOTaskProgressUpdate; + diff --git a/web/app/src/manager-api/model/TaskProgressUpdate.js b/web/app/src/manager-api/model/TaskProgressUpdate.js new file mode 100644 index 00000000..ac5b38dc --- /dev/null +++ b/web/app/src/manager-api/model/TaskProgressUpdate.js @@ -0,0 +1,75 @@ +/** + * Flamenco manager + * Render Farm manager API + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + * + */ + +import ApiClient from '../ApiClient'; + +/** + * The TaskProgressUpdate model module. + * @module model/TaskProgressUpdate + * @version 0.0.0 + */ +class TaskProgressUpdate { + /** + * Constructs a new TaskProgressUpdate. + * TaskProgressUpdate is sent by a Worker to update the progress of a task it's executing. + * @alias module:model/TaskProgressUpdate + * @param progress {Number} Indicates the percentage of the task that's been completed. + */ + constructor(progress) { + + TaskProgressUpdate.initialize(this, progress); + } + + /** + * Initializes the fields of this object. + * This method is used by the constructors of any subclasses, in order to implement multiple inheritance (mix-ins). + * Only for internal use. + */ + static initialize(obj, progress) { + obj['progress'] = progress; + } + + /** + * Constructs a TaskProgressUpdate from a plain JavaScript object, optionally creating a new instance. + * Copies all relevant properties from data to obj if supplied or a new instance if not. + * @param {Object} data The plain JavaScript object bearing properties of interest. + * @param {module:model/TaskProgressUpdate} obj Optional instance to populate. + * @return {module:model/TaskProgressUpdate} The populated TaskProgressUpdate instance. + */ + static constructFromObject(data, obj) { + if (data) { + obj = obj || new TaskProgressUpdate(); + + if (data.hasOwnProperty('progress')) { + obj['progress'] = ApiClient.convertToType(data['progress'], 'Number'); + } + } + return obj; + } + + +} + +/** + * Indicates the percentage of the task that's been completed. + * @member {Number} progress + */ +TaskProgressUpdate.prototype['progress'] = undefined; + + + + + + +export default TaskProgressUpdate; +