From b31c664afb7f4354314309afd0539f5262681618 Mon Sep 17 00:00:00 2001 From: MKRelax Date: Wed, 1 Mar 2023 17:41:35 +0100 Subject: [PATCH 1/9] Worker: use BLENDER_CMD environment variable if available --- internal/find_blender/find_blender.go | 5 +++ internal/find_blender/find_blender_test.go | 18 +++++++++++ internal/worker/command_blender.go | 9 ++++-- internal/worker/command_blender_test.go | 36 ++++++++++++++++++++++ 4 files changed, 65 insertions(+), 3 deletions(-) diff --git a/internal/find_blender/find_blender.go b/internal/find_blender/find_blender.go index a5503a2c..dbd896b2 100644 --- a/internal/find_blender/find_blender.go +++ b/internal/find_blender/find_blender.go @@ -50,6 +50,11 @@ func FileAssociation() (string, error) { return fileAssociation() } +// EnvironmentVariable returns the content of the BLENDER_CMD environment variable. +func EnvironmentVariable() string { + return os.Getenv("BLENDER_CMD") +} + func CheckBlender(ctx context.Context, exename string) (CheckBlenderResult, error) { if exename == "" { // exename is not given, see if we can use .blend file association. diff --git a/internal/find_blender/find_blender_test.go b/internal/find_blender/find_blender_test.go index 3c3d2b34..8ee8a09d 100644 --- a/internal/find_blender/find_blender_test.go +++ b/internal/find_blender/find_blender_test.go @@ -5,6 +5,7 @@ package find_blender import ( "context" "flag" + "os" "os/exec" "testing" @@ -41,3 +42,20 @@ func TestGetBlenderVersion(t *testing.T) { assert.ErrorIs(t, err, exec.ErrNotFound) assert.Empty(t, version) } + +func TestGetBlenderCommandFromEnvironment(t *testing.T) { + + // Without environment variable, we expect an empty string + path := EnvironmentVariable() + assert.Equal(t, "", path) + + // Try finding the blender path in the BLENDER_CMD environment variable + err := os.Setenv("BLENDER_CMD", "/path/specified/in/env/to/blender") + if err != nil { + t.Fatal("Could not set BLENDER_CMD environment variable") + } + + path = EnvironmentVariable() + assert.Equal(t, "/path/specified/in/env/to/blender", path) + +} diff --git a/internal/worker/command_blender.go b/internal/worker/command_blender.go index 9895e8b5..cea06368 100644 --- a/internal/worker/command_blender.go +++ b/internal/worker/command_blender.go @@ -85,9 +85,12 @@ func (ce *CommandExecutor) cmdBlenderRenderCommand( } if crosspath.Dir(parameters.exe) == "." { - // No directory path given. Check that the executable can be found on the - // path. - if _, err := exec.LookPath(parameters.exe); err != nil { + // No directory path given. Check that the executable can be found in the + // environment variable BLENDER_CMD or on the path. + if path := find_blender.EnvironmentVariable(); path != "" { + logger.Info().Str("path", path).Msg("found Blender in environment") + parameters.exe = path + } else if _, err := exec.LookPath(parameters.exe); err != nil { // Attempt a platform-specific way to find which Blender executable to // use. If Blender cannot not be found, just use the configured command // and let the OS produce the errors. diff --git a/internal/worker/command_blender_test.go b/internal/worker/command_blender_test.go index 0c7cac22..49b342f2 100644 --- a/internal/worker/command_blender_test.go +++ b/internal/worker/command_blender_test.go @@ -2,6 +2,7 @@ package worker import ( "context" + "os" "testing" "github.com/golang/mock/gomock" @@ -79,6 +80,41 @@ func TestCmdBlenderCliArgsInExeParameter(t *testing.T) { assert.Equal(t, ErrNoExecCmd, err, "nil *exec.Cmd should result in ErrNoExecCmd") } +func TestCmdBlenderFromEnvironment(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + ce, mocks := testCommandExecutor(t, mockCtrl) + + envExe := `D:\Blender_3.2_stable\blender.exe` + + // This should use blender.exe from the BLENDER_CMD environment variable + err := os.Setenv("BLENDER_CMD", envExe) + if err != nil { + t.Fatal("Could not set BLENDER_CMD environment variable") + } + + taskID := "c5dfdfab-4492-4ab1-9b38-8ca4cbd84a17" + cmd := api.Command{ + Name: "blender", + Parameters: map[string]interface{}{ + // Passing "blender", the environment path should override this + "exe": "blender", + "argsBefore": []string{}, + "blendfile": "file.blend", + "args": []string{}, + }, + } + + mocks.cli.EXPECT().CommandContext(gomock.Any(), + envExe, // from environment variable + "file.blend", // from 'blendfile' + ).Return(nil) + + err = ce.cmdBlenderRender(context.Background(), zerolog.Nop(), taskID, cmd) + assert.Equal(t, ErrNoExecCmd, err, "nil *exec.Cmd should result in ErrNoExecCmd") +} + func TestProcessLineBlender(t *testing.T) { ctx := context.Background() mockCtrl := gomock.NewController(t) -- 2.30.2 From 4dd489ac01ef8125f2acae21313fb07933f3b876 Mon Sep 17 00:00:00 2001 From: MKRelax Date: Fri, 3 Mar 2023 15:24:38 +0100 Subject: [PATCH 2/9] Replaced BLENDER_CMD environment variable with FLAMENCO_BLENDER_PATH (using a constant) --- internal/find_blender/find_blender.go | 6 ++++-- internal/find_blender/find_blender_test.go | 6 +++--- internal/worker/command_blender.go | 4 ++-- internal/worker/command_blender_test.go | 6 +++--- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/internal/find_blender/find_blender.go b/internal/find_blender/find_blender.go index dbd896b2..21820af6 100644 --- a/internal/find_blender/find_blender.go +++ b/internal/find_blender/find_blender.go @@ -24,6 +24,8 @@ var ( ErrTimedOut = errors.New("version check took too long") ) +const BlenderPathEnvVariable = "FLAMENCO_BLENDER_PATH" + // blenderVersionTimeout is how long `blender --version` is allowed to take, // before timing out. This can be much slower than expected, when loading // Blender from shared storage on a not-so-fast NAS. @@ -50,9 +52,9 @@ func FileAssociation() (string, error) { return fileAssociation() } -// EnvironmentVariable returns the content of the BLENDER_CMD environment variable. +// EnvironmentVariable returns the full path of a Blender executable, if given as environment variable func EnvironmentVariable() string { - return os.Getenv("BLENDER_CMD") + return os.Getenv(BlenderPathEnvVariable) } func CheckBlender(ctx context.Context, exename string) (CheckBlenderResult, error) { diff --git a/internal/find_blender/find_blender_test.go b/internal/find_blender/find_blender_test.go index 8ee8a09d..bb94ef04 100644 --- a/internal/find_blender/find_blender_test.go +++ b/internal/find_blender/find_blender_test.go @@ -49,10 +49,10 @@ func TestGetBlenderCommandFromEnvironment(t *testing.T) { path := EnvironmentVariable() assert.Equal(t, "", path) - // Try finding the blender path in the BLENDER_CMD environment variable - err := os.Setenv("BLENDER_CMD", "/path/specified/in/env/to/blender") + // Try finding the blender path in the environment variable + err := os.Setenv(BlenderPathEnvVariable, "/path/specified/in/env/to/blender") if err != nil { - t.Fatal("Could not set BLENDER_CMD environment variable") + t.Fatal("Could not set blender executable in environment variable") } path = EnvironmentVariable() diff --git a/internal/worker/command_blender.go b/internal/worker/command_blender.go index cea06368..13e5c72e 100644 --- a/internal/worker/command_blender.go +++ b/internal/worker/command_blender.go @@ -85,8 +85,8 @@ func (ce *CommandExecutor) cmdBlenderRenderCommand( } if crosspath.Dir(parameters.exe) == "." { - // No directory path given. Check that the executable can be found in the - // environment variable BLENDER_CMD or on the path. + // No directory path given. Check that the executable is set in an + // environment variable or can be found on the path. if path := find_blender.EnvironmentVariable(); path != "" { logger.Info().Str("path", path).Msg("found Blender in environment") parameters.exe = path diff --git a/internal/worker/command_blender_test.go b/internal/worker/command_blender_test.go index 49b342f2..3ed10fc1 100644 --- a/internal/worker/command_blender_test.go +++ b/internal/worker/command_blender_test.go @@ -10,6 +10,7 @@ import ( "github.com/rs/zerolog/log" "github.com/stretchr/testify/assert" + "projects.blender.org/studio/flamenco/internal/find_blender" "projects.blender.org/studio/flamenco/pkg/api" ) @@ -88,10 +89,9 @@ func TestCmdBlenderFromEnvironment(t *testing.T) { envExe := `D:\Blender_3.2_stable\blender.exe` - // This should use blender.exe from the BLENDER_CMD environment variable - err := os.Setenv("BLENDER_CMD", envExe) + err := os.Setenv(find_blender.BlenderPathEnvVariable, envExe) if err != nil { - t.Fatal("Could not set BLENDER_CMD environment variable") + t.Fatal("Could not set blender executable in environment variable") } taskID := "c5dfdfab-4492-4ab1-9b38-8ca4cbd84a17" -- 2.30.2 From cbbe96d5009c0fc01653851a5302f8f81dcdfc2a Mon Sep 17 00:00:00 2001 From: MKRelax Date: Fri, 3 Mar 2023 16:12:42 +0100 Subject: [PATCH 3/9] Clear environment before test, use assert.noError() --- internal/find_blender/find_blender_test.go | 20 ++++++++++++-------- internal/worker/command_blender_test.go | 4 +--- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/internal/find_blender/find_blender_test.go b/internal/find_blender/find_blender_test.go index bb94ef04..dc037bc6 100644 --- a/internal/find_blender/find_blender_test.go +++ b/internal/find_blender/find_blender_test.go @@ -45,17 +45,21 @@ func TestGetBlenderVersion(t *testing.T) { func TestGetBlenderCommandFromEnvironment(t *testing.T) { - // Without environment variable, we expect an empty string + // If the environment variable is unset or empty, we expect an empty string + err := os.Unsetenv(BlenderPathEnvVariable) + assert.NoError(t, err, "Could not clear blender executable from environment") path := EnvironmentVariable() assert.Equal(t, "", path) - // Try finding the blender path in the environment variable - err := os.Setenv(BlenderPathEnvVariable, "/path/specified/in/env/to/blender") - if err != nil { - t.Fatal("Could not set blender executable in environment variable") - } - + err = os.Setenv(BlenderPathEnvVariable, "") + assert.NoError(t, err, "Could not set blender executable in environment") path = EnvironmentVariable() - assert.Equal(t, "/path/specified/in/env/to/blender", path) + assert.Equal(t, "", path) + // Try finding the blender path in the environment variable + envExe := `D:\Blender_3.2_stable\blender.exe` + err = os.Setenv(BlenderPathEnvVariable, envExe) + assert.NoError(t, err, "Could not set blender executable in environment") + path = EnvironmentVariable() + assert.Equal(t, envExe, path) } diff --git a/internal/worker/command_blender_test.go b/internal/worker/command_blender_test.go index 3ed10fc1..7f93618a 100644 --- a/internal/worker/command_blender_test.go +++ b/internal/worker/command_blender_test.go @@ -90,9 +90,7 @@ func TestCmdBlenderFromEnvironment(t *testing.T) { envExe := `D:\Blender_3.2_stable\blender.exe` err := os.Setenv(find_blender.BlenderPathEnvVariable, envExe) - if err != nil { - t.Fatal("Could not set blender executable in environment variable") - } + assert.NoError(t, err, "Could not set blender executable in environment") taskID := "c5dfdfab-4492-4ab1-9b38-8ca4cbd84a17" cmd := api.Command{ -- 2.30.2 From dc1077c4cf1c741fc419439390e31e84ab5fb58d Mon Sep 17 00:00:00 2001 From: MKRelax Date: Fri, 3 Mar 2023 16:33:03 +0100 Subject: [PATCH 4/9] Changed event message to "using blender from FLAMENCO_BLENDER_PATH" --- internal/worker/command_blender.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/worker/command_blender.go b/internal/worker/command_blender.go index 13e5c72e..193a33ef 100644 --- a/internal/worker/command_blender.go +++ b/internal/worker/command_blender.go @@ -6,6 +6,7 @@ package worker import ( "context" + "fmt" "os/exec" "regexp" "sync" @@ -88,7 +89,8 @@ func (ce *CommandExecutor) cmdBlenderRenderCommand( // No directory path given. Check that the executable is set in an // environment variable or can be found on the path. if path := find_blender.EnvironmentVariable(); path != "" { - logger.Info().Str("path", path).Msg("found Blender in environment") + msg := fmt.Sprintf("using blender from %s", find_blender.BlenderPathEnvVariable) + logger.Info().Str("path", path).Msg(msg) parameters.exe = path } else if _, err := exec.LookPath(parameters.exe); err != nil { // Attempt a platform-specific way to find which Blender executable to -- 2.30.2 From 9d77497e5193a571f6f093c6957770fb9dd496fc Mon Sep 17 00:00:00 2001 From: MKRelax Date: Sat, 4 Mar 2023 01:50:50 +0100 Subject: [PATCH 5/9] Fix worker output by checking the environment in CheckBlender() --- internal/find_blender/find_blender.go | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/internal/find_blender/find_blender.go b/internal/find_blender/find_blender.go index 21820af6..fe47906b 100644 --- a/internal/find_blender/find_blender.go +++ b/internal/find_blender/find_blender.go @@ -52,12 +52,26 @@ func FileAssociation() (string, error) { return fileAssociation() } -// EnvironmentVariable returns the full path of a Blender executable, if given as environment variable +// EnvironmentVariable returns the full path of a Blender executable, by checking the environment. func EnvironmentVariable() string { return os.Getenv(BlenderPathEnvVariable) } func CheckBlender(ctx context.Context, exename string) (CheckBlenderResult, error) { + + // First check the exename, if given some form of path + if crosspath.Dir(exename) != "." { + // exename is some form of path, see if it works for us. + return checkBlenderAtPath(ctx, exename) + } + + // Check the environment for a full path + envFullPath := EnvironmentVariable() + if envFullPath != "" { + // The full path was found in the environment, report the Blender version. + return getResultWithVersion(ctx, exename, envFullPath, api.BlenderPathSourceInputPath) + } + if exename == "" { // exename is not given, see if we can use .blend file association. fullPath, err := fileAssociation() @@ -74,11 +88,6 @@ func CheckBlender(ctx context.Context, exename string) (CheckBlenderResult, erro } } - if crosspath.Dir(exename) != "." { - // exename is some form of path, see if it works for us. - return checkBlenderAtPath(ctx, exename) - } - // Try to find exename on $PATH fullPath, err := exec.LookPath(exename) if err != nil { -- 2.30.2 From b385c00c4369247d5e7b85606f0b00d3fb882af3 Mon Sep 17 00:00:00 2001 From: MKRelax Date: Sun, 25 Feb 2024 21:17:11 +0000 Subject: [PATCH 6/9] OAPI: Add env_variable to BlenderPathSource --- pkg/api/flamenco-openapi.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/api/flamenco-openapi.yaml b/pkg/api/flamenco-openapi.yaml index a3f73671..e50ceaa2 100644 --- a/pkg/api/flamenco-openapi.yaml +++ b/pkg/api/flamenco-openapi.yaml @@ -1505,6 +1505,8 @@ components: - path_envvar # The input path was used as-is, as it points to an existing binary. - input_path + # The input path was found in the $FLAMENCO_BLENDER_PATH environment variable. + - env_variable WorkerRegistration: type: object -- 2.30.2 From 2b5f27c2b89277bd9d715a57af10c69f277120cd Mon Sep 17 00:00:00 2001 From: MKRelax Date: Sun, 25 Feb 2024 21:17:39 +0000 Subject: [PATCH 7/9] OAPI: regenerate code --- .../manager/docs/BlenderPathSource.md | 2 +- .../manager/model/blender_path_source.py | 9 +- pkg/api/openapi_spec.gen.go | 390 +++++++++--------- pkg/api/openapi_types.gen.go | 2 + .../manager-api/model/BlenderPathSource.js | 7 + 5 files changed, 210 insertions(+), 200 deletions(-) diff --git a/addon/flamenco/manager/docs/BlenderPathSource.md b/addon/flamenco/manager/docs/BlenderPathSource.md index c0150a8a..b02ac255 100644 --- a/addon/flamenco/manager/docs/BlenderPathSource.md +++ b/addon/flamenco/manager/docs/BlenderPathSource.md @@ -4,7 +4,7 @@ ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -**value** | **str** | | must be one of ["file_association", "path_envvar", "input_path", ] +**value** | **str** | | must be one of ["file_association", "path_envvar", "input_path", "env_variable", ] [[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/model/blender_path_source.py b/addon/flamenco/manager/model/blender_path_source.py index 134c535b..f23ab8b1 100644 --- a/addon/flamenco/manager/model/blender_path_source.py +++ b/addon/flamenco/manager/model/blender_path_source.py @@ -55,6 +55,7 @@ class BlenderPathSource(ModelSimple): 'FILE_ASSOCIATION': "file_association", 'PATH_ENVVAR': "path_envvar", 'INPUT_PATH': "input_path", + 'ENV_VARIABLE': "env_variable", }, } @@ -106,10 +107,10 @@ class BlenderPathSource(ModelSimple): Note that value can be passed either in args or in kwargs, but not in both. Args: - args[0] (str):, must be one of ["file_association", "path_envvar", "input_path", ] # noqa: E501 + args[0] (str):, must be one of ["file_association", "path_envvar", "input_path", "env_variable", ] # noqa: E501 Keyword Args: - value (str):, must be one of ["file_association", "path_envvar", "input_path", ] # noqa: E501 + value (str):, must be one of ["file_association", "path_envvar", "input_path", "env_variable", ] # noqa: E501 _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. @@ -196,10 +197,10 @@ class BlenderPathSource(ModelSimple): Note that value can be passed either in args or in kwargs, but not in both. Args: - args[0] (str):, must be one of ["file_association", "path_envvar", "input_path", ] # noqa: E501 + args[0] (str):, must be one of ["file_association", "path_envvar", "input_path", "env_variable", ] # noqa: E501 Keyword Args: - value (str):, must be one of ["file_association", "path_envvar", "input_path", ] # noqa: E501 + value (str):, must be one of ["file_association", "path_envvar", "input_path", "env_variable", ] # noqa: E501 _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. diff --git a/pkg/api/openapi_spec.gen.go b/pkg/api/openapi_spec.gen.go index 47cc6901..262f75ef 100644 --- a/pkg/api/openapi_spec.gen.go +++ b/pkg/api/openapi_spec.gen.go @@ -49,201 +49,201 @@ var swaggerSpec = []string{ "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", - "/xMAAP//yoC45FYOAQA=", + "giolE041kmSzmwsmrq5oObCI4eQLJq4urmjJYW8xsu2shJ3jsQ9IyYxKBhI3JQpNUta2BeTvA0sqzTZZ", + "L/tNg57QB48dyONkKPgkdkovylKW3f38yAQreUKYeUxKpgopFIvZWdMI5v90dnZC0BhIzBtemvcDkWPD", + "WZOsStFqgndklUmaEiURyT0AcbUN2GaZXRoXaLbk0qiZz8xkD/cPPRPypoaUajqlqHpOK7UyzIoRWKhb", + "lOVlUmjKBaHk3lumy9Xo6Uyz8h6+umAUrBlmeVykPKGaKWuvQoVV8xzVb3MUTHldtGS65Cwdk5eguDop", + "xQ7IFcgxBk2okZUda7+nLBs07yYZZwKsKKkkSubM6IlzUjKqJBgrCEhX7APeJU4zMqXJpZzNkIF6+66T", + "LLvG5ZwpRecx3GshF5x7/X4Us66Y0D/L6bvC8PCo4qGY9obaITFHCzo7OZXJJdPHb/Ze/9vZGZ4hSpIo", + "aCgDxZIItjQ/qiGZFCW74rJSF4h0E29nYR8QxxACbYNYxjS7sAfF0gsa4RDHM6uaZgy4j6G8/gsrCDlr", + "Bs+Z0jQviKHQiA0GURwmmE+VliXKRi8zmjORSM+0m2dkYDYyI0aZToQCvXt3/NxJdD+DUX6DPb8Wk5oD", + "/ULzUBmMmx4a4N5Ey43s5H0RoXfDKyYP92PYWLJZydTiAmy5kaPxF9CLk/aKqAXYh+33QC3sbu4ptAzX", + "sipgHSoWytw2A3g1NEgHMmhKQaNgNFnAjb/iaUUz9EotYZa5IZVgLpHS3OCVG8Rah4uSJmC16rVS7A7E", + "fl8OTB1BjzOPnHJGMqq0XeXWOLek6gJvTNrjNMErarD8vVGc7cv1HTG3XUsy0WXFJlbZsE8KlvAZNy+D", + "bgYWRZ7eq23CiumhJavmJrnbnRd6tZUVDy6AA07gqLLup8BB1US6XsL2iir91hou+yicRVBZ1ghqIF8b", + "PHlO5zVzdNCzy4xL8Vu56oYDvajyqaA82wKtwq0cmxWB0yEm3+NcVF3af/lJ+sHEZ+zZKomJx54AZnzG", + "Rol5ibAr0OutHd1ogsDS1KJCxT6VSzE0kkUJf1bFkDCdxIj7NlY7vzhYKmo5rV33mtjwE6ouX8l53/mD", + "EzuTc5IsKnFpGZyWhBLga1oWPNlzvI6UUuYkZUjTUnzPCkAG5EP45Ury1IyTggDRIjgxOGQyov0/M+tx", + "NF7bVY7Ja7ry4k9eZZoXIFMIpuBd9kFH1Q2HEGtZErj7hzv6mGtUM9tYewzbSBlnAMYNYgaAoyNnADW4", + "rqBh6P9V06G/PS/fDnDDXYjDZr6vcdLPZfzNKITrfHNT/CzGHjyFs5pThF34k+zFRVTpzmgvUcAXyBmd", + "b0BFrj0axugbWvXWQdIvZVv2Dfa8Ldn3ZpbbZ+sKwLTNpcU3N17bJYJ1DcQSKi6M9EBLvc5Ww5WdEjQ3", + "Wmk5sl/FzTUWTlHlwcmYaNZmulZH7XINtO0A4y8m/ePyt6EZ5t5cKMYisSBGKHDKLFfhes37zoARGBy3", + "W/tm0rN0q/9c4oNg2JX8xL+6QLza5eNn8MVb1P1uVjS/YqWyUR1bkLl+6ubGGTbuSuwOO9ntr/XM1r1v", + "/jnn4Gs6HB+MHj8azdP08EH68PAHN/fR4P+VVelwZwChGaX2mxgcjg9HNCsWdD9YU/gz+a4z9vfOI17f", + "d1jFTs6BxjI+roVz8wQtGLwk7x00OaNWu1xUORVGulJVDp+hbFGyjFHFyLTiWeqCHMExYq4EVWQSrmqC", + "orEEUlV/AlE31pqGX0/mXE+I/QpsZFEfSgs96vNvgMKjjIFoDBt+xgBJmmVvZoOjv62/I6fO42O++jT8", + "uEZWWusDcNoUcV8QKbweFZVTMaohZrw1D8BB5W7i1lfvn96GdA3jxc6EcPwZQp079A3i3KffEI//nMnk", + "MuNK9zvgkEFZoxMtGVhuIZqRpSRhJahPoEWgm04a8cRaOBKHnFv5QML1vBC6XMXcH92XOk619eG/uJ9t", + "dQf7dg8RbZ1APXQY7dtDQp7b6xEPeTS/EjqVlcZ4RKd3WenJSVbWjMIbYlUTGmpBcyoukgVLLmWl1/vt", + "TuFl4l4OolncAkqWyyuWEppJMcfgXxd+sE1wWXMtPaCJW2g6C38hZDVfhC4RYBc08BwUnCWMaDnHLaZ8", + "NmMlmEzhBMFmab4mlCwkmKoyqvkVI+/evnJ+iIgNa0zOJDA3iHzBAJC3r4bmp4RqJqhm5HzwcUoV+7T3", + "UQov7alqNuMfmPp0PojJ7OaDJlqWWZQK2WEa7sUNsdato4CpgpF6juI1Vcph6inLWBIPpT7xXjcMBTbP", + "psxS9PdyqpyNukZhgy6BEAWyuaVZFzn9MDgaHOwfHI72H43275/dPzy6/+Do/sN/3T842t/vCj/drztB", + "glmGC0GHMitZSHLNwmayBE+146s1b2pdvh3ocxSkTNOUagrsP00hAJBmJxFzXoPxNjZTTrkuabkiuR3M", + "IfSYvDbbMNQ1Yx/C0CzrmMul2QXEUFSKizmZ0PF0nEwMWa/vkMHVS7ZqnVFRStjH0eC0KLlm5GXJ5wtt", + "mI1i5ZjlYIAdqNW0ZOL/ntowAlnO3RtWHj6FF8ip/t//64plgx44nVgj9TOvizTPPPSs5PQDz6t8cHR/", + "f384yLnAvyJultY18IP04P9pEEETPyxdVqznWy8rOPc8MGAMIxCJOQZMBSnQTjEczCjHHwtaKfjH3ytW", + "4WvwxcjLUQPcB6sYhs9VBtYjT5Oa0bo1Hvll9UEV3avxgAx8FoR9W5c3hkN9EXGpzTCc6GKX1XdKWpa9", + "bMI+BD7hg/RcwLUXKc31qBRExyGLM28hP2ApmfGMKWS6giVMKVquYgS8xeCiZuJ7zxx3PX5+L3Dbg+jm", + "HOVtRhxmdozJU240IYErdZ/EmLazv1ghwTHvWSlzv/U+VSkG6DOqLtVplee0XMVykvIiA8cWyaz0iHkp", + "Dupj8gzt7RjSYK3MLqzR/OQOCRyQ5vk4Ygq07tGthEqwr9oFbxHT1csI1b9VDPccMi2eG6374XCQB0S9", + "j0x+Gg4gW+ZiuoKMMsuuINrVIvpv3gLDRYNgeDpgScRvXRaIa/lYU7/78ZCHz+Y+L3mmjUJec5+h4yWv", + "jv/yomYl0Rh6OZsp1lxo1Bteg+rjDvlkakt63bejMCxzl10Fp9a+FW+ZrkqBRlGQQEBopo56cituwBZ2", + "0ZXa7vEAqfsRuC8QEVB/2zuFpoxr3qWIFzLgkBjuXI7AFlcVg2H9y6LSqVzG2Zo1CDyTYsbnVUmdlNrc", + "JFcvean020pssIhzBdI9R5HfENCZ+bCOdrLzkbISQWyFT0gC8YqSGVuSGTWkWA2JDQUXUowga89oIUm4", + "XmAyRgB1SrUPD54yiMnIC21IunlLL9jKitTiniZT1htsAXwEk7vSrXQ/WIUuqVAzVpKnJ8eQ1+DCY8c9", + "IR3AYl/JhMb1g+eeJQG/M9zM3DSYy3483mjgaM/S3t0wPOAY6tlT+6sLeYwgyIVeyiWN8LY3go2WdEVc", + "vCQGbUNWn1Qagh6lueQ2fwwyHjgkgJUMMgNzCLwxjHfy0cjBnyZWweQlZqw5kWQBOSLKeXpcargP1HU+", + "ojE5W8rImsA8aidNO7kCXvphdvlFRrXRZkbeZoM5myAu2EGmK7/oPkSDjzabSKxptQa0+3KL83papZyJ", + "ZsCrtU5ZBUOtIw5uGLWO9a0je2306TDG17QoDIzhlN2hELNlyAPTPruMY4p2ZMOrvzBWvK2EiCZ91yFg", + "y+DiWmdVTlfkkrHCECXhhMK4CJV35ukeaK0I9Ej1DY9PjLi0AtZoU1+oTcJe41xavD72IW0gkS8YmSy9", + "q4lNiNmKtWCHecd4fcwkAO+5NP8V7INuBF+hQ3dIJk0gTMjrd6dnRkOeQELfZKs4qxYgPdT6YBTDch/z", + "feyC9lt6rg2QX3+xWjlhkeFvPQfhq6UKgCbE0s0cxcbBbxfg/5bNDdsuWWo9zh1I0jQtmVI7lr+w9Dd+", + "0+RML2nJ1lzDnT28Lo3mwpuo1W4y9mcV0LAMwIEqLKLhADEcJJiHeWHjcjwUelYfO61TllQl1ysf8N+i", + "gNtGfq8L+T5luiqeKsWVpkKj8BnLlQiFPDk1sp3TwUHuMqMQP0yXWltD2gtIpqBbJNf2J5N8LUGtu4Uo", + "PEGce9brqTjFIBlrjLGuB16S05+eHjx8hNdeVfmQKP4PSFadriC42QhkNgeeZHZRLgujazVpGT1hNnDz", + "IvkZ1Gnb47lEIXRwNDh8ON1/8OR+cvB4un94eJjen00fPJwl+49/eELvHyR0/9H0fvrowX568PDRk8c/", + "7E9/2H+csof7D9LH+wdP2L4ZiP+DDY7uPzh4AH5inC2T8zkX83CqR4fTxwfJo8PpkwcHD2bp/cPpk8PH", + "+7Ppo/39R0/2f9hPDun9h4/vP05mhzR98ODg0eHD6f0fHieP6A9PHu4/flJPdfD4U9eQ4CByEqW25tdA", + "enSKkOXXYSa9G8cVy/C+FetXaZu4gIZT5ZUi9PmGYTfkWBCsr2F99cr5VexYGLvjQrrMg3O/HXL8/HyA", + "xiancvuAAZ+2QnEVoKtNrB1npLJqvgdFF0aGeu1h4YLR8fNJT6amRZkttWlc+0uesdOCJRsVaxx82Dym", + "zbep5v4xu655hla61qnEKgldAz2sW7qNGKA4W9DXvjm9oMJ6PZuRA1Q1BgW3jM2wpa6cRH2NyVkgXXw+", + "8m0RULLlkfij7hI4q4JRJ3VRpLyWVtlFB3Q4Lim2HPmyHg9NGfWI3hMbrSBDIytsktpwzOgYQGc+ds1t", + "rEmjBxsdNWY1drxhv7DbBPCvXC9qJ8xWoHZKeOK8lVHQD62YOiQpK2x0OtAR5xP5xs9mW9kzOI4e/07n", + "VENT+Lrj7fjWKnEp5FJA5EsmaYr6GAYPRc0CONhbXA1UjXHRi9cVPEDQaMCuV5a4IaHhVgSEW2Bv/Yff", + "PC/MZI1zNTwtELMpKYPPHEsZhkdpbROyed1ZeWXkjpc8Y0EEFCCa4ST2NfObS4io5fowi/i2cKC+mP4+", + "3AxahBP56/aFcSUg35+LNVgtsUk42l5iPP9dee6XIoRriV7J0tNNmlublSj4rOZYNDVCsdXpggg9aq2q", + "5Lza3z945O3BVjqrlMH8jqFZSztgZC4Upvw9sALUPdV0d0QzhwIL7w6WWG8Y/jQcZAGAdrS13IKrpHXq", + "Wa0h+603DCHNNUWxw2aHnFbTNaUuT5kAK77PvsMQOQUh13sq+HaCSYm2EJmWtgCRo5LBm+bhezn12Xjk", + "mRsT6ybNmQ6fo+oFpl6qLn3SsPs7k3OFbi3BmC0eUWQ84TpbuWmnDKPIwbFiHq2GfiNGi8C8E/euGUMK", + "jH34TktYT2PqmctUfS+n3wPvNq+bV+4pyGMEo7XmORufC+fjE1KjaWS6grRG0EosH6GaFKXUMpGZq/bj", + "oYW+GQSmL+cLGT3TUkLGjxm5GZPRvByy2EhlIrjwxtnKt63tFhvEVcRxlr/+MGqs0aBl8xj2SCXqHwxl", + "GO+cHCmLdSXg1m89EBP9MiBmqv4rKiH2gSJCHKgml1ykNidiaxj4yLAs+1lOIUg7y371Ti1bkICqy0zO", + "8WEYHBu+fkbncfdXIwMhWnertmgFBaq0rLGxKcFsE+vy+SGB9sHh7/8f+a9///0/fv/P3//H7//xX//+", + "+//8/T9////DHHaophDGfcAsoPUcDfYwcHdPzfbey6lCM879g8MxvARmlEpcXqBccxjg5MkvPxoULdTg", + "yIhVUKvTSDv3R/f3sRzfBSRosaXyJSAhNhhL9LEPmgmbyTMurGvIrORCVtqX4GmsD6fwK9yL79zWEuyM", + "V0qp145nC0RiZbqLmhMOMi6qD8H1A6/1yB6VDXzuRtyGSLAhVsQHvG5bBXxDnYzwrDfFyLhXa9v3VpE1", + "dThhD9Q64QFIa8ScqJXSLK8Dvu23rWpxEGaYyLnginXFK/tyHTNNSSaXrBwlVDFvtrRTuEXZEJNzPNDz", + "wZCcD5ZcpHKp8I+Ulksu8N+yYGKqUvMH08mYnPqpZF5QzX1l7x/lPUUmZSWAD/745s3p5E+krASZgH9V", + "ZiTlSkO8HwQ0GC5LffifK6rrF6nG5+KpcvInzYjZ0bCxD3LuYn7OB844aAuUo23GhWNDqcKihHwIqsj5", + "oCltuvHOBzXsc6mMPAFizSUjmim9l7JpNbcVEBVhVHGoNWilERcXit5rnpBUJlBjFhJdsqyxs2i5gL5E", + "FPPDxfblCockkQUPFcxJu2jd2Iw28SVsuwUPz+xfdTKHId4sJdz6x7EASSqZEvc0yalOML2DJrqimR+p", + "Y5g/w9K5IDqqdh1EwCOZpUFgXbPkebsMpS957UqDnIvjxgK5IjJHPjWsbWVQ62pVUKVatY476TxRoNs0", + "aE3nKMrZ2+dqmNXRt0H6+PFzH5pja7lY3o3qI9XEF42cMmJITFpleP3NUtBoCOEJGN0ly2BjBrtc9pVB", + "Q/eFX0kz/W0rKcq6X7t1YCJELiZnxdtYnLm6Gti4AuLblNOgnbnelSQbEj5mY5dw4cNkgjCp8W4lJb5k", + "84ubSJrEkN2L6erCRSvtErxsgw0ia90yhW2HShmQRqNlZfB0Q74iRqeJlU+VN/+X1skzNu5otzT5r98b", + "5KZyNR3p2eXEt83vbBfyiLUlCZuP+Mu0oQ+JLfezMUERkuSk7UESlPD5rIpOce+EITRgYG8V8xk2LO5d", + "TAlq9mycuSqz+MTv3r4K05Tr2QnXimUz78mUS5FJmm4TgVSX/PGniDl/sP++U/mMzCKfSKDkTI/aCUcx", + "/bGe8C7lDIW3+hpJQ2FaSFcnrpQmrJtdWqM75jvLRu3uutweiL9d7N+xXNFdIobXTUffkiK5mfpOal3F", + "MXzmSxtC4L0T5aSl0qiKIeZZMzfYG4FiwYlB7VEU9bCRiZHs/emB7U4WGDD8JyKtiaT1Ap8LqFTwHcg3", + "0kVcTxy9tdWzhNSEldRGtvpyDm2p3Szr+03ltbox6hkXtu2Ejb6FSIp7iiS+twEGmPMwfRvINXlzxcpl", + "yTVDWZ7LSkEhHxFUnXB5plHxIVZ87ZWc26JqngZgfTcnFbuWCGbRcCowIaNlxnuKUOsGCdyBSkSRq47m", + "jOoDJYOwlISBTgjKOxcYlY/jRJz96wJBP48KrLlkbtLYJar3uF3VEhs06vPmOokSxUWwx5ZkcELss06F", + "prUOme0MKv1jfX5gq6ax9jJnFCmF4/t1xSxo+JGzfIp4upVI36hS1l0AalfbDKAutyO5wVE1XEtB9Zto", + "TO2n34aRFPouO3TUtkazV9vUE+leml2VozaOrvcQu9H7bwfGdwceg9ribW3R9peRr9kVsaIqlpQMOKUc", + "CalHmmXZiIqVFCyMZD4aHI4P+mB/9DcXMGskt1lesLntBjOq24EMhoOcqySSCXrNUHO78I9f/ma15TOc", + "qenojE1hkbn/yE75XLxpH1aj8J21zNsDfHpyDE3TgpO4qCtuqSWdz1k5qvgNHUyrJF83waG/VldntTd/", + "TI6QxE+ms6I1p5QxVpxa21fEN20ee9uYC09ANdJlup0amIGLlokU0zC9fOPqSPm08ZSumnqaH9sQbFCU", + "xuRpUWSc2VqFmCcvzYcc7FaTlK7UhZxdLBm7nEC4H7zT/N287GoyR1YIMqEgBw9GC1mV5Kefjl6/rrOI", + "sa9OjbbhyIOjQS6JrgjEUYCbML0AqftocP+Ho/19TFqxSp9NaQa8cm/tP4nWSWlO0o2JpAkbKVbQEqN1", + "l3KUMehk5OrlWKhDcWK6Qr7I2GUPmMl354NcosdBV87Z8P2YvABrZ86oUOR8wK5YuTLjuao43d6Qfv+B", + "6AQA7ck8cqD5GC9A7gG1ebg2j/VjD5vQbIwbrHjNvdBUsz6d2iaUl2F63fZpPlGNOBhsq0WlLcLqI77o", + "kl6yLnJdJx9p+zCoxnehQ99WxzYSMKxrOKDKkBRzCJD8MxxopuwrcjYzyggYB9r1HmsE6i9sGcnux0p1", + "SLZqxdMmOdYhwVBM1pZRjtgG1EVG/7FaH3bUzJ+0/gnU5sIug0Cuag8LSiu1BmgVXkVmXHC16OsLOfyC", + "5zn0+1tzsn3WmD9TxZM1guf4M0rfLncpfbuLEf2rVJn9UhmCX6wG7DYVRH0FnpZmVfqc2mvYmbYv7Vrr", + "YzHFL1RYyFN0VlLhTUHZysZRrpy0QeeE68BxD1VZwLYx9q5BayYujMAgZ3XpeaN+EsXN31QwML50pYSO", + "Rtaoz2iGTiX58eQdwcANb+V58eKvL16M65q0P568G8FvESGh2Tl551Kams7H5JntSWu9ma0SR9RWmUfD", + "vU25oOBmL6lIZU5gQG8ism3yt/J4bms72aBbnNH5lqS/pvYeCVTHTmB3YBCheaKazi94CrrFg8P7B+mj", + "H5IRo4/S0YOHjx6Nnkxnj0bsyWz/yZQ9+CFh04ha4UcIRP3NHTPWif5uxLXQcWp+ZzG7qvBRY8inNVOj", + "kWQ7S1az/tPH6zqk4t1BIkaSM3SD+9MO2NQn1LIhLdmoQ3lo97igVSxB6J1iJRSQsAVzLcs4fj4kBVVq", + "KcvUl1AGtdrWCTH6j7Nf1mYNg3oAGOBshq/WO11oXQw+fYLmgejwg94YiQ4MIJ5WnzGaW1cVfqmO9vZm", + "LlyQy71ucQyMWSQvaZnbMFgImR4MBxlPmM3i8MTp1dVhZ/zlcjmei2osy/me/UbtzYtsdDjeHzMxXugc", + "iwlynTVWm/vS27Wyf3+8PwYFSRZM0IKDRcb8hHlIcDJ7tOB7V4d7Sbus0BwNJb4OxXEKPeR0s/4QyJiQ", + "AgKjHezvO6gyAd9To4NiBPjee+tBQ7zdMgC+OR8cXhPowmB15lNREAWdoGVWjNEzzQz1Wae7Jl7qv0HQ", + "HxCgeowXIi0kt1W/57a7emfATuVmA/koePcglGfPmVn6gP2Si/TPPqn8BDPHbgzc8d6OEXi/lJWoc8xB", + "PfbdNOFlG9j4hdaFxQ0i6zj17fKWRuJfllLMx63Tf8ltxLssSS5LRp69OnbNG9FZA3FviiwpRMyBDOW2", + "E0OKQqrISUECcuSogHf+WaarLwaNViGVCFhc20pZWl8fRB5h8RCJQWRY+ubm8ahRmKG70l+aF3eIi8Qw", + "NzjSGRfs7uHUX2nGweFKQ2y6DjK18NR6ba/q8V1P7fogNxIVTFMaBYHAa1C2kXb1VbH25Nbw858CMTE7", + "rcbIZvLaBna3wzi9yIipCVtKES8xe/uzjnyHwsWfho2xVjTPmmO15eJNCNI+iLfQGPaKxQWPrpyw9jSe", + "JglTyjeMjVRTjAxJwlQu3Ng98Om/KZh4enLsEtWyTC5texHXFH/PSpL2QCekoMmlOexz0X/ciumqGFFX", + "36ef7JzSKxYtKXQzhCc6VZRphmA1tJteIXq3kPJBpNNRCxkgAn3JprQonJEkNSrSrMqyun+ptpXGjFx5", + "90jJuzqkqCe1FSsOWasTNLkRsMMVmVUiwZsIhdg3oLdBiBhm91aO6sfBBufb++iyTT/tfXRO2E/rSFKD", + "GTa7bBsFnBvY2fINVoUL8llrxdk6qnZRcbo5vkaLj0wYOJP7J2xTr99ukJnG87Z3p5hOS2slWWeNfO+w", + "C1Mj09t8aU0CLtHbIKfP8kbb/4763brlNGqL9yZ/96OqT4LaHUvrCp//jaHX2ID6DOSsKwO0zQfknaoT", + "np3QTtN0hMxkTRYcklFfHJRNMeNrRqGli2EcseQRMqWqrt40LeVSNdLBro/x9R53x3FXX7uH80PyDbag", + "uhFW32hC1j3kn+XU5ivnXHfQ8yY1jjULArdYZSQ85J02S8yIaja8NWhOrgDaD+4f3LyMcOYpqk+HY5rO", + "IWsOZMo6ba75QjRpjmPP52xF0spXJ7MNjBKaLBzy+aHgPkhJMiOanItbFY/gAXElMZuUAHHMenagZqQs", + "O3cE6zpAQl0o+2Cx+MZwPzdzCJm9lJ1Lhar9FlcL9Nqve7+SYAnrrteDeJr+jhfCZ3saKop9OBZGoPzl", + "zRlmV9rGejZ9oU7P0wtZzRf/faH+KBcK0GrDdQLs9/s2I4EpDUqoLLk5cV17Z3nkmjW6oPWb5ZlOFj9m", + "ckobdSoghexmuUhfV/8tBJph/Mqdue56Lh0abg8Vq2hHuB65CPrIQTYxK69st9LI52rD8b2BqsHYHafO", + "QpoDoHuW0zq/nCo1wgZmuFX3r+YBQq83Zhu/3RC17G0rF7V9NhvLNWu9Y0M3aRuzja9NWhU2hAuJa04h", + "n9XcFNfI1FLER7dCEUuGaxIyaFtXE0J7LuM7Q61e0/ISVxqCbFhL466rSVJyzUpON2A8jJeb27bToMgD", + "nLRQJ1xhAQPDFABVHCW0VamgkJk5cfN73jz0LsmFQYtSou1xwfy7PuV9SpPLeSkrkY7PxS8S5qN4Zyft", + "VoUT4lVVCHsyX7GUVAXISkLzElz7UqSuLEhOET3Ra9cBD9bPXcmKsA8FS/QQqzswXpJJ3XNqUieyK1t7", + "1yhpGe6JQhNXmLVl2wRi8nfXCysuc0GnIVvO6IYIiG3HFTPhtQu7NknFnOnxbWs4jdZL/SwJoBp4Vmyc", + "GFaGgIoqfGaQGUQYIAW2ORF8eHdIAQgBvgSMAfx23K1ujjWDflwQKCZSoiQE+HZ5mhHf9j6a//5Cc7bW", + "NGQrpGxlGHID3hk7TbvOS6+Kgc/acojNpfACr4EpNKPxkNhwPkGuf7O1M5aViZ6L2uI01OAWgRa1bvmX", + "/G5UBIABKtsm16BSAUndGoj1VJ6h+PG6IPyIEWaftpLVtsJqX1+gH6c3xcD9to049RxJUEDHPGPydX10", + "yedzI63eLtF6J5AjspRAZkDXN4kBnQEnRRVgSLhIsipF5UhZbRr6fBl1QM6x2DCq3LZWkh/EsGsXpN8R", + "D8gv0jfYUJ0u39+tmP6+abD0mNWvf31VjLgV0yBH3a7LdFoKkutKvt7MhB+JlAQ5fH33cW/a7Jgfv5lv", + "oc9qo7/+bR7IjUhc9VZiCktVGPz9DmNOh7Y+xqpg3xuZK2gb732XHo5bepLd3aRJwgooj8WELjmzRi0g", + "K3aSu0ZUoJuwW62tR27ufACCXe/318Grm7voa5ELbClrEMyoVnOpEZ5BDSq4/XcJFZBGgQmomQxfl5Z3", + "ewA0SSUE01od129ZNXe4XurACBmPat4954ATp3I7WPvatjc09X0LSPkHNyk2j/oa5sXooI1G5P0IpJgO", + "yxX1+GZAEzipawL9wVmk24nN6e1xdQi2JA421zRZuol83hFVnjGilfLgoK8cl2u66ZbgIuHwex9H+5WJ", + "5hpk9ZJAvQULhma8y0YErbMj16Hnqa9d9cdGzkYJtx7UbCYYQ3SGNTNfC01PG8NdB0mbC7KYCp4rf9gu", + "q1n5Bh5e8v+DoHFzk7sgMeihG9nzGbz1bfBk2IvP54vLighjzlRYSk11JJ87JhZSu24oAEezLFx1Axu2", + "kffiO44j0XJB9Wgpqyy1/sFRKntxytucfl1Q/av56Fg//1YEPueR7JPzsFeCNetEbBAG+QIZClsYukxw", + "Z9OBRGgcBSIRXFVpF62BtUSHYGfK5NxGwfXKY2Aysh1X6lnq4dCwBPULhXd/pSSRwuUEZCs3BVdBa23r", + "fXDV6rErIgqestI9RqkvA4sQV7EDzp5rhreHBXDXMO1mD9kbivdpThLzQoUd41yMBrENNW/P+RTtARqL", + "8Xd9MKF9tm3WGbjDkV/vP7l5YulXQrOS0XRli4lbgeHBrfre8fQgBE3MIZCVTFQLonVbuUlwTRDlebIg", + "Uljz/q2xm6rFblpE6hm26KV1p1S8/mqVZ1xc+ugC6JaMEMD4Mo1ExQKlMqJLlgXWN+wDh9TCNsiyNd4T", + "mmX+gteRfDX9QKC2sx/sgihR4WWCxTQ6N9OS0bU0I2z+ty3lCE/2RqlIrAHltgTlK9CSaP/F2HqrqT02", + "6O0hQZwPD2IY1hIz79iGhdaVcqeuDPT3rJsjhzCwXWMx4aeQpVb24teM125sI8I/xYwz6qIVPdtoD+hb", + "zLkISOxTiauoyQ68q7QREPwSurcEht376HqYftr7CL/wf6xxqIftDGXJXGhtSwbcujstFE/tCozu1Z38", + "8MPOvEG5eNfY0VeKj8zqdr/NrHWz4t9u/OJ1WlhuaYi8U5coLGNWt9qMNl1tCJjBfVlHvD1G/nMj4zBm", + "VLFExZXNtD4H2/o+ZTNWEt/J1fXayWzG5vngYP+H84FHrDquDpQK8O/pqhROpK+3p7wch2GVvnVu58Ax", + "Eo9mSuIYSuZMCkZYpmCcun55bJmALQDABaNYUsCC8P8Z4TSjZ1SMnpt9jt7BAIMIDINGnTEYypLPuaAZ", + "zGnGh9Y9WCA9k2FBdd9imOugX5VtEcxDqm2VPFcDSxDK4Q1oSzXnGJO+aW9v7MJGL+3CBhtjlbaRZ2Si", + "mR4pXTKaNymE19SnXJj7PdycGP4M51CtvuTXsCs6MbRrUjzY/2HT6xYdG4hoSQ7G9z6OjlDaz406gGG4", + "U6aXzCK7BWcQDeS1dhsOMvN91WXZoTtedHa4DMrOw0gXIrzELnV6/a11N7C+ORbxXOyqnJEpMx/6+aer", + "xr1DiWLSe4WOiDmzia1gCNSlEZ18y9kUGzgQcAabT9HPd0gzXrfxEO7nTJYJn2YrkmTSNnH46ezshCRS", + "CAxkd82RJBSatITXVttUjfNihH2giSaK5sxKklq6RmoklZUR8vADBU1o8S1MNcTbVNcajJwAmcp01ctK", + "w5x2M0WtXXTBEkqOYF3c+2h713xab4CGcm1bhV36Vjh300BoS+5HHSdYUlXM5B21LDebMq0x20W+WHPy", + "e7bjx/rTdz2kvhUkcPtZhwvQFcrhQ09AU1tigg8XVBEBjVDIium7hU5hBEKnARdGaucMsxJw7xscYLYS", + "TCvswA053oB4GloLb4F8Z+bFu4N8mn3Qe0VGudixss5ZGzjfCl4FcVFUaTJjS9sxKEAybMm+FfUKP/Hj", + "uS5Ea7Fqu6CAoKnQrWLVl7dAdlq7ffNxAcgCv4HAAOzY5fPBwAzPZjOWaCfWQhdeHIEqsmRZ1s6OM98y", + "aitdLKqcCoUx0CCcggv5itNu9Y26lLW5I1DY3t0oDGiEi1XfqwnhQmlG27lkQXnw3pIuvpD3jbF0l47h", + "prp2GVWf19Fo0F2XQllfdgRVO+UbTmOnNGcC1ja13ecx0nq6iISOxzDK53pP07k5ifl22SR1ReZtFXFN", + "53Vix12OwA5L7kOJcrgMlcBizarRbtmHqZvdoW3fjKEgNb4+xhrMG0K214D1yyFyUE07TsaDzUdQ2Av9", + "4Wu9e92G782/ANsrqghMsQRbE6hfnjtuhKfNpm0B7JoGLYNptlulv05YoePuZHba0ndUoFce6uRtgywN", + "RBvabUKbEpuOTZu42UfINsS6+QNTt3LNXvXkK9SN5NV4TTbhMnyt/57FK9SCE/+rX4DdEP8WKR103a9D", + "WdAe6uJaoEmH8i6LIVGytvclNMusoe9SyCWEYb17d/z87lxCH8Ah2HLX64eSSBP14rct6Ma46cLdwm3r", + "u2p/ASu+W+umu6a2gpFNhnCfOlG34TCIlbHvAm/vo+3tsIPotZVK6Ye9+XTeTr1nizueR9lYvrsp8Tlt", + "aWn7CB5rvPmJzHPfdBh8mAmE3IIDxdZorQ0oS9/GhQsysS3EJqBcoQew+RKGXNj+RUPDxAvCNZnxUukx", + "eSpWaJHB18JWIcEwzmcIZL3yPbquJ3d+VZz60qRgDcfdNi146fuGbSOvkJRpaJPvj9jZdbe7+dtYlazO", + "322mddtHd1NCRLRB2F0wNt0RO1AvAm5nDXIYvRNSOoG619DZkKe/CTTsNPXqwcGujE6On6uGCaH2u7oe", + "4ETO/jlxNKiIbiCF0FALXngL2K+742fGWDFSQdfgTVyu2Wb4W2J5zZ1t05QDgloafZXXJSWzUKgTMvbl", + "3UTBDZTrq2LEjXHSTcjgcozbp3hty5Tv6/xV7VLXpE1GgJOls6w1+uFG0LzlxsDeeax0Hf/XyG/4ope3", + "b+783wb9/NZZnyRxq79V04yDBEv7xfWOO+XuxIi55TfMKx1FoSOj1UdiWF79pYogldH3RnI2WyN68bl4", + "M5tt5YK5e7C0HS6BxDZ6W/4N2mW2SnwGOi9VpG7PvRbgz2iWYbSis85oSTLrhnNlOsF8pxdsda9kZA6l", + "VOzw495TERsORdzo1bZT9F/qnGmaUk2/grE1bFb/h7jSW6Ph00ovmNAQFe/6zBlscKGUfdaCz8ZJDETW", + "EmawObgy4FS8PvAoxmqbCBsVjINTG3xt5ICVOu3GB3H0CqRCkv4v7jZW7Y4hLsPLd/kvMWtCrHqA0IsK", + "I3wz7SdhncNKBzdt8/ETxbSW2n+hPJ7uLKH+gSmPper23Jw9GcISEm9cUIQmhmxkLMXahJg4ZSnKqBkT", + "5dAFfKtc1Ak7lsqwcpTJhGZA4GimvjRVu2KN3VQx9xIEB63hs1Yet3HjN1cf1hree8O6odxa0K6kj1z9", + "Il09UJ+W6YtkBXaPB/uHX7B1H6JYL2KesNJ1TnnOBEfSafP346ZzDKGzLI8mml+hJZaBe9TViMoyuURf", + "hQWL3XrJ5wtNhFzaAL7D22Uw7iJRATlp6MAzUjisDjPLIGN9LqElu83MwAu346W17kHqxw+gsek2AU45", + "hbOMN7WJRtD1XxczJNrfvoVgVLuTvutoZSMucIkuMPBaVg07Vjf6NHZL6hwP1WzubzHJlaVU0uZz+bHr", + "0mq3bTD5TObUMOqqyyHRq4InEHtouw2BwFyUcl4ypYbQjsg1aJAlmVGeVSXbyGEcX1FMpA1HnQG3Gx2q", + "R7OSbb4pezldjfiorPrDSl/TlTWlVOKbSEp5TVd/Yax4ix7nb0w9w8BvK8bU2cuBxBy43gMGVVaC7JFL", + "xgrniq8DwMmbwtU+gkQ6yoUilKCrPZRJvVMm5n/vQeSORA/KXrCy1pq4qqPS16O2rHRR6VFRyrRK1gn6", + "hli+gZdP3Lt3gjlAzaq99wWb75pNPLTfFmL+tRKRD7ZMRAbpz6bYurYVD+7fv/mL9oqJuV744j1/Cjuf", + "pTzFfteGylJiQTCyn2BeuV3p4c2v9ISuIN8U2q7R0varenD/4W24EVRVFLI0B/WapZySs1VhPWaAYgQx", + "ygmTU58uXXcxDaO/Hhw8uZ0Oea5+A3JKIB1SYoekmbnYtlCcdUvrRSm1zpgtJ/eHkjwwT9sAOpdKk5Il", + "mL3uS9/BflEeCLK1OQAH+yaZj2tHCBMKa9dhDgVI7/aUzZf3FEn5nCkofts+Y/LMZ89DnNjJLz8CnH8+", + "efEjsahkBi0yKkQ8TmudwKMXVT4VlGdqryjZFWdLR5Z4iQX/HLUnSP2dGAQQLa8cNa/KbHA02BsERqg2", + "sTpuBkF12lo5TPHsAJJUuoUwfpZTZyYFGe3vFSu5Qb+6Xeew1U5h3KgCqSKDPj05bvY3DE1kMs8rgeIm", + "FNjotPRvO3AjE1hseO3XRKDVf293YWzGZLZh7kopM7eizmTgdIyUesH0eT8L8Ik6999C0PdcfC+nvqJZ", + "OIdN1//026f/EwAA//9lf+c1ZQ4BAA==", } // 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 5590d81b..1c1e506f 100644 --- a/pkg/api/openapi_types.gen.go +++ b/pkg/api/openapi_types.gen.go @@ -48,6 +48,8 @@ const ( // Defines values for BlenderPathSource. const ( + BlenderPathSourceEnvVariable BlenderPathSource = "env_variable" + BlenderPathSourceFileAssociation BlenderPathSource = "file_association" BlenderPathSourceInputPath BlenderPathSource = "input_path" diff --git a/web/app/src/manager-api/model/BlenderPathSource.js b/web/app/src/manager-api/model/BlenderPathSource.js index 5d2a1366..67d2ab25 100644 --- a/web/app/src/manager-api/model/BlenderPathSource.js +++ b/web/app/src/manager-api/model/BlenderPathSource.js @@ -40,6 +40,13 @@ export default class BlenderPathSource { "input_path" = "input_path"; + /** + * value: "env_variable" + * @const + */ + "env_variable" = "env_variable"; + + /** * Returns a BlenderPathSource enum value from a Javascript object name. -- 2.30.2 From d37d33a8b927c563c433e09144f205fb80613e15 Mon Sep 17 00:00:00 2001 From: MKRelax Date: Sun, 25 Feb 2024 21:17:43 +0000 Subject: [PATCH 8/9] Return blender path from environment (api.BlenderPathSourceEnvVariable) --- internal/find_blender/find_blender.go | 28 +++++++++--- internal/find_blender/find_blender_test.go | 51 +++++++++++++++++++--- internal/worker/command_blender.go | 12 +++-- internal/worker/command_blender_test.go | 6 ++- 4 files changed, 81 insertions(+), 16 deletions(-) diff --git a/internal/find_blender/find_blender.go b/internal/find_blender/find_blender.go index fe47906b..907091cc 100644 --- a/internal/find_blender/find_blender.go +++ b/internal/find_blender/find_blender.go @@ -53,8 +53,24 @@ func FileAssociation() (string, error) { } // EnvironmentVariable returns the full path of a Blender executable, by checking the environment. -func EnvironmentVariable() string { - return os.Getenv(BlenderPathEnvVariable) +func EnvironmentVariable() (string, string, error) { + + // Check the environment for a full path + fullPath := os.Getenv(BlenderPathEnvVariable) + if fullPath == "" { + return BlenderPathEnvVariable, "", nil + } + + // Make sure the path exists and is not a directory + stat, err := os.Stat(fullPath) + if err != nil { + return BlenderPathEnvVariable, fullPath, err + } + if stat.IsDir() { + return BlenderPathEnvVariable, fullPath, fmt.Errorf("Path from environment is a directory: %s", fullPath) + } + + return BlenderPathEnvVariable, fullPath, nil } func CheckBlender(ctx context.Context, exename string) (CheckBlenderResult, error) { @@ -66,10 +82,12 @@ func CheckBlender(ctx context.Context, exename string) (CheckBlenderResult, erro } // Check the environment for a full path - envFullPath := EnvironmentVariable() + _, envFullPath, err := EnvironmentVariable() + if err != nil { + return CheckBlenderResult{}, err + } if envFullPath != "" { - // The full path was found in the environment, report the Blender version. - return getResultWithVersion(ctx, exename, envFullPath, api.BlenderPathSourceInputPath) + return getResultWithVersion(ctx, exename, envFullPath, api.BlenderPathSourceEnvVariable) } if exename == "" { diff --git a/internal/find_blender/find_blender_test.go b/internal/find_blender/find_blender_test.go index dc037bc6..e3a26c7f 100644 --- a/internal/find_blender/find_blender_test.go +++ b/internal/find_blender/find_blender_test.go @@ -7,6 +7,7 @@ import ( "flag" "os" "os/exec" + "runtime" "testing" "github.com/stretchr/testify/assert" @@ -45,21 +46,59 @@ func TestGetBlenderVersion(t *testing.T) { func TestGetBlenderCommandFromEnvironment(t *testing.T) { - // If the environment variable is unset or empty, we expect an empty string + // If the environment variable is unset, we expect an empty string err := os.Unsetenv(BlenderPathEnvVariable) assert.NoError(t, err, "Could not clear blender executable from environment") - path := EnvironmentVariable() + envVar, path, err := EnvironmentVariable() + assert.NoError(t, err) + assert.Equal(t, BlenderPathEnvVariable, envVar) assert.Equal(t, "", path) + // If the environment variable is empty, we expect an empty string err = os.Setenv(BlenderPathEnvVariable, "") assert.NoError(t, err, "Could not set blender executable in environment") - path = EnvironmentVariable() + envVar, path, err = EnvironmentVariable() + assert.NoError(t, err) + assert.Equal(t, BlenderPathEnvVariable, envVar) assert.Equal(t, "", path) - // Try finding the blender path in the environment variable + // Try finding a non-existing executable in the environment variable envExe := `D:\Blender_3.2_stable\blender.exe` err = os.Setenv(BlenderPathEnvVariable, envExe) assert.NoError(t, err, "Could not set blender executable in environment") - path = EnvironmentVariable() - assert.Equal(t, envExe, path) + envVar, path, err = EnvironmentVariable() + assert.Error(t, err) + assert.Equal(t, path, envExe) + assert.Equal(t, BlenderPathEnvVariable, envVar) + + // Try finding a existing file in the environment variable + // We use this filename instead of a real blender executable + _, myFilename, _, _ := runtime.Caller(0) + err = os.Setenv(BlenderPathEnvVariable, myFilename) + assert.NoError(t, err, "Could not set blender executable in environment") + envVar, path, err = EnvironmentVariable() + assert.NoError(t, err) + assert.Equal(t, path, myFilename) + assert.Equal(t, BlenderPathEnvVariable, envVar) + + if !*withBlender { + t.Skip("skipping test, -withBlender arg not passed") + } + + existingPath, err := exec.LookPath("blender") + if err != nil { + existingPath, err = fileAssociation() + if !assert.NoError(t, err) { + t.Fatal("running with -withBlender requires having a `blender` command on $PATH or a file association to .blend files") + } + } + + // Try finding an existing executable in the environment variable + err = os.Setenv(BlenderPathEnvVariable, existingPath) + assert.NoError(t, err, "Could not set blender executable in environment") + envVar, path, err = EnvironmentVariable() + assert.NoError(t, err) + assert.Equal(t, path, existingPath) + assert.Equal(t, BlenderPathEnvVariable, envVar) + } diff --git a/internal/worker/command_blender.go b/internal/worker/command_blender.go index 193a33ef..decd0cb0 100644 --- a/internal/worker/command_blender.go +++ b/internal/worker/command_blender.go @@ -88,10 +88,14 @@ func (ce *CommandExecutor) cmdBlenderRenderCommand( if crosspath.Dir(parameters.exe) == "." { // No directory path given. Check that the executable is set in an // environment variable or can be found on the path. - if path := find_blender.EnvironmentVariable(); path != "" { - msg := fmt.Sprintf("using blender from %s", find_blender.BlenderPathEnvVariable) - logger.Info().Str("path", path).Msg(msg) - parameters.exe = path + envVar, envPath, err := find_blender.EnvironmentVariable() + if err != nil { + return nil, err + } + if envPath != "" { + msg := fmt.Sprintf("using blender from %s", envVar) + logger.Info().Str("path", envPath).Msg(msg) + parameters.exe = envPath } else if _, err := exec.LookPath(parameters.exe); err != nil { // Attempt a platform-specific way to find which Blender executable to // use. If Blender cannot not be found, just use the configured command diff --git a/internal/worker/command_blender_test.go b/internal/worker/command_blender_test.go index 7f93618a..83c6b7a6 100644 --- a/internal/worker/command_blender_test.go +++ b/internal/worker/command_blender_test.go @@ -3,6 +3,7 @@ package worker import ( "context" "os" + "runtime" "testing" "github.com/golang/mock/gomock" @@ -87,7 +88,10 @@ func TestCmdBlenderFromEnvironment(t *testing.T) { ce, mocks := testCommandExecutor(t, mockCtrl) - envExe := `D:\Blender_3.2_stable\blender.exe` + // We pass this file as blender executable because it must exist + // envExe := `D:\Blender_3.2_stable\blender.exe` + _, myFilename, _, _ := runtime.Caller(0) + envExe := myFilename err := os.Setenv(find_blender.BlenderPathEnvVariable, envExe) assert.NoError(t, err, "Could not set blender executable in environment") -- 2.30.2 From 46ab0075fb82b8b353d59169c642446e9a7a3bd1 Mon Sep 17 00:00:00 2001 From: MKRelax Date: Sun, 25 Feb 2024 21:17:46 +0000 Subject: [PATCH 9/9] Added env_variable to Manager Setup Assistant --- web/app/src/views/SetupAssistantView.vue | 32 ++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/web/app/src/views/SetupAssistantView.vue b/web/app/src/views/SetupAssistantView.vue index 29d1751c..39eca8b3 100644 --- a/web/app/src/views/SetupAssistantView.vue +++ b/web/app/src/views/SetupAssistantView.vue @@ -115,6 +115,30 @@

Choose how a Worker should invoke the Blender command when performing a task:

+ +