diff --git a/benchmark/space/__init__.py b/benchmark/space/__init__.py index 849c02e..79a4dc6 100644 --- a/benchmark/space/__init__.py +++ b/benchmark/space/__init__.py @@ -348,7 +348,11 @@ class BENCHMARK_PT_main(Panel): sub = col.row() sub.enabled = not G.results_submitted sub.scale_y = 2.25 - sub.operator("benchmark.share", text="SHARE ONLINE") + if G.submission_exception: + text = "Retry Submission" + else: + text = "SHARE ONLINE" + sub.operator("benchmark.share", text=text) sub = col.row() subsub = sub.split() @@ -631,10 +635,20 @@ class BENCHMARK_OT_share(bpy.types.Operator): make_buttons_default() print('Submitting benchmark') + G.submission_exception = None try: submission.submit_benchmark(G.result_dict) + except submission.CommunicationError as cex: + logger.ERROR(f'Error {cex.status_code} submitting benchmark: {cex.message}') + if cex.json: + logger.ERROR(f'Response JSON: {cex.json}') + else: + logger.ERROR(f'Response body: {cex.body}') + G.submission_exception = cex + return {'CANCELLED'} except Exception as ex: - self.report({'ERROR'}, f'Error submitting results:\n{str(ex)[:100]}') + logger.ERROR(f'error submitting benchmark: {ex}') + G.submission_exception = ex return {'CANCELLED'} print('Submission done') make_buttons_green() diff --git a/benchmark/space/draw.py b/benchmark/space/draw.py index 96e0d54..3b5de76 100644 --- a/benchmark/space/draw.py +++ b/benchmark/space/draw.py @@ -4,11 +4,12 @@ import blf import bpy from ..foundation import util +from ..submission.client import CommunicationError from . import G -WELCOME_TEXT = "Run the Quick Benchmark on the selected device to\n" \ +WELCOME_TEXT = "Run the Quick Benchmark on the selected device to " \ "get a fast measurement of your hardware's performance.\n" \ "Usually takes less than 30 minutes." @@ -147,7 +148,9 @@ def _draw_introduction(image_y, ui_scale, window_width, window_height): x = 50.0 * ui_scale y = image_y - (image_y - 52 * ui_scale - 18 * 3 * ui_scale) * 0.5 blf.size(font_id, int(12 * ui_scale), 72) - draw_text_multiline(WELCOME_TEXT, x, y) + + text = word_wrap(WELCOME_TEXT, window_width * 0.45) + draw_text_multiline(text, x, y) def _draw_benchmark_is_running(image_y, result_platform, result_stats, ui_scale, window_width): @@ -208,4 +211,74 @@ def _draw_benchmark_has_run(image_y, ui_scale, window_width, window_height): x = 50.0 * ui_scale y = image_y - (image_y - 52 * ui_scale - 18 * 3 * ui_scale) * 0.5 blf.size(font_id, int(12 * ui_scale), 72) - draw_text_multiline(BLURB_TEXT, x, y) + + text = _after_submission_text() + text = word_wrap(text, window_width * 0.45) + draw_text_multiline(text, x, y) + + +def _after_submission_text() -> str: + ex = G.submission_exception + + # Nothing wrong, just show the default text. + if not ex: + return BLURB_TEXT + + # If not our own exception class, show generic message. + if not isinstance(ex, CommunicationError): + return f'Error submitting your results: {ex}' + + # Return proper message based on the HTTP status code of the response. + if ex.status_code in {502, 503}: + # 502 Bad Gateway, happens when restarting uWSGI. + # 503 Service Unavailable, happens when OpenData is down. + text = f'There was a hickups with code {ex.status_code} ' \ + f'in the Open Data platform, please try submitting again.' + elif ex.status_code == 422: + # 422 Unprocessable Entity, happens when the JSON doesn't pass schema validation. + text = f'We somehow submitted invalid data, see below for details.' + else: + text = f'Error {ex.status_code} submitting your results.' + + if not ex.json: + return text + + msg = ex.json.get('message') + if msg: + return f'{text}\nThe server said: {msg}' + return text + + +def word_wrap(string: str, width_in_px: int) -> str: + """Word-wrapping with variable character width. + + Newlines in the input string are kept in the output. + """ + + if '\n' in string: + # If the string already consists of multiple lines, wrap each line individually. + return '\n'.join(word_wrap(line, width_in_px) + for line in string.splitlines(keepends=False)) + + # Do an estimate of the maximum number of characters to fit on a line. + char_width = blf.dimensions(font_id, "i")[0] + max_chars = int(width_in_px // char_width) + + wrapped_lines = [] + while string: + # The line won't be longer than max_chars, so start there. + candidate_line = string[:max_chars] + line_width = blf.dimensions(font_id, candidate_line)[0] + + # Keep removing the last word until the line fits the width. + while line_width >= width_in_px: + marker = len(candidate_line) - 1 + while not candidate_line[marker].isspace(): + marker -= 1 + candidate_line = candidate_line[:marker] + line_width = blf.dimensions(font_id, candidate_line)[0] + + string = string[len(candidate_line):] + wrapped_lines.append(candidate_line.strip()) + + return '\n'.join(wrapped_lines) diff --git a/benchmark/space/global_state.py b/benchmark/space/global_state.py index c8bf91f..376fbf1 100644 --- a/benchmark/space/global_state.py +++ b/benchmark/space/global_state.py @@ -1,4 +1,5 @@ import threading +import typing class G: @@ -19,6 +20,8 @@ class G: current_progress = 0.0 progress_lock = threading.Lock() + submission_exception: typing.Optional[Exception] = None + @classmethod def reset(cls): """Reset the global state.""" @@ -29,3 +32,4 @@ class G: cls.background_image_path = "" cls.scene_status = {} cls.results_submitted = False + cls.submission_exception = None diff --git a/benchmark/submission/__init__.py b/benchmark/submission/__init__.py index 2275121..1ad8eaa 100644 --- a/benchmark/submission/__init__.py +++ b/benchmark/submission/__init__.py @@ -1,3 +1,6 @@ +from .client import CommunicationError + + def submit_benchmark(benchmark_data: dict): """Submit benchmark data to MyData. @@ -8,7 +11,7 @@ def submit_benchmark(benchmark_data: dict): import logging import os - from .client import CommunicationError, BenchmarkClient + from .client import BenchmarkClient mydata_url = os.environ.get('MYDATA') or 'https://mydata.blender.org/' if 'MYDATA' in os.environ: