We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Async download Scout on start
When mindsdb-http api starts, it checks new version of gui is available. If it available, mindsdb starts download it. Issue here - is mindsdb-http server is not start until gui-downloading finishes. Because of this launch time of http-server can be long.
To fix it, need run initialize_static (https://github.com/mindsdb/mindsdb/blob/staging/mindsdb/api/http/start.py#L24) function in separate process/thread.
Also, it will be good to make concurrent download of GUI files here: https://github.com/mindsdb/mindsdb/blob/staging/mindsdb/api/http/initialize.py#L141

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `mindsdb/api/http/initialize.py`
Content:
```
1 from distutils.version import LooseVersion
2 import requests
3 import os
4 import shutil
5 import threading
6 import webbrowser
7 from zipfile import ZipFile
8 from pathlib import Path
9 import logging
10 import traceback
11 
12 from flask import Flask, url_for
13 from flask_restx import Api
14 from flask_cors import CORS
15 
16 from mindsdb.__about__ import __version__ as mindsdb_version
17 from mindsdb.interfaces.datastore.datastore import DataStore
18 from mindsdb.interfaces.native.mindsdb import MindsdbNative
19 from mindsdb.interfaces.custom.custom_models import CustomModels
20 from mindsdb.utilities.ps import is_pid_listen_port, wait_func_is_true
21 
22 
23 class Swagger_Api(Api):
24     """
25     This is a modification of the base Flask Restplus Api class due to the issue described here
26     https://github.com/noirbizarre/flask-restplus/issues/223
27     """
28     @property
29     def specs_url(self):
30         return url_for(self.endpoint("specs"), _external=False)
31 
32 
33 def initialize_static(config):
34     ''' Update Scout files basing on compatible-config.json content.
35         Files will be downloaded and updated if new version of GUI > current.
36         Current GUI version stored in static/version.txt.
37     '''
38     log = logging.getLogger('mindsdb.http')
39     static_path = Path(config.paths['static'])
40     static_path.mkdir(parents=True, exist_ok=True)
41 
42     try:
43         res = requests.get('https://mindsdb-web-builds.s3.amazonaws.com/compatible-config.json')
44     except (ConnectionError, requests.exceptions.ConnectionError) as e:
45         print(f'Is no connection. {e}')
46         return False
47     except Exception as e:
48         print(f'Is something wrong with getting compatible-config.json: {e}')
49         return False
50 
51     if res.status_code != 200:
52         print(f'Cant get compatible-config.json: returned status code = {res.status_code}')
53         return False
54 
55     try:
56         versions = res.json()
57     except Exception as e:
58         print(f'Cant decode compatible-config.json: {e}')
59         return False
60 
61     current_mindsdb_lv = LooseVersion(mindsdb_version)
62 
63     try:
64         gui_versions = {}
65         max_mindsdb_lv = None
66         max_gui_lv = None
67         for el in versions['mindsdb']:
68             if el['mindsdb_version'] is None:
69                 gui_lv = LooseVersion(el['gui_version'])
70             else:
71                 mindsdb_lv = LooseVersion(el['mindsdb_version'])
72                 gui_lv = LooseVersion(el['gui_version'])
73                 if mindsdb_lv.vstring not in gui_versions or gui_lv > gui_versions[mindsdb_lv.vstring]:
74                     gui_versions[mindsdb_lv.vstring] = gui_lv
75                 if max_mindsdb_lv is None or max_mindsdb_lv < mindsdb_lv:
76                     max_mindsdb_lv = mindsdb_lv
77             if max_gui_lv is None or max_gui_lv < gui_lv:
78                 max_gui_lv = gui_lv
79 
80         all_mindsdb_lv = [LooseVersion(x) for x in gui_versions.keys()]
81         all_mindsdb_lv.sort()
82 
83         if current_mindsdb_lv.vstring in gui_versions:
84             gui_version_lv = gui_versions[current_mindsdb_lv.vstring]
85         elif current_mindsdb_lv > all_mindsdb_lv[-1]:
86             gui_version_lv = max_gui_lv
87         else:
88             lower_versions = {key: value for key, value in gui_versions.items() if LooseVersion(key) < current_mindsdb_lv}
89             if len(lower_versions) == 0:
90                 gui_version_lv = gui_versions[all_mindsdb_lv[0].vstring]
91             else:
92                 all_lower_versions = [LooseVersion(x) for x in lower_versions.keys()]
93                 gui_version_lv = gui_versions[all_lower_versions[-1].vstring]
94     except Exception as e:
95         log.error(f'Error in compatible-config.json structure: {e}')
96         return False
97 
98     current_gui_version = None
99 
100     version_txt_path = static_path.joinpath('version.txt')
101     if version_txt_path.is_file():
102         with open(version_txt_path, 'rt') as f:
103             current_gui_version = f.readline()
104     if current_gui_version is not None:
105         current_gui_lv = LooseVersion(current_gui_version)
106         if current_gui_lv >= gui_version_lv:
107             return True
108 
109     log.info(f'New version of GUI available ({gui_version_lv.vstring}). Downloading...')
110 
111     shutil.rmtree(static_path)
112     static_path.mkdir(parents=True, exist_ok=True)
113 
114     try:
115         css_zip_path = str(static_path.joinpath('css.zip'))
116         js_zip_path = str(static_path.joinpath('js.zip'))
117         media_zip_path = str(static_path.joinpath('media.zip'))
118         bucket = "https://mindsdb-web-builds.s3.amazonaws.com/"
119 
120         gui_version = gui_version_lv.vstring
121 
122         resources = [
123             {
124                 'url': bucket + 'css-V' + gui_version + '.zip',
125                 'path': css_zip_path
126             }, {
127                 'url': bucket + 'js-V' + gui_version + '.zip',
128                 'path': js_zip_path
129             }, {
130                 'url': bucket + 'indexV' + gui_version + '.html',
131                 'path': str(static_path.joinpath('index.html'))
132             }, {
133                 'url': bucket + 'favicon.ico',
134                 'path': str(static_path.joinpath('favicon.ico'))
135             }, {
136                 'url': bucket + 'media.zip',
137                 'path': media_zip_path
138             }
139         ]
140 
141         for r in resources:
142             response = requests.get(r['url'])
143             if response.status_code != 200:
144                 raise Exception(f"Error {response.status_code} GET {r['url']}")
145             open(r['path'], 'wb').write(response.content)
146 
147     except Exception as e:
148         log.error(f'Error during downloading files from s3: {e}')
149         return False
150 
151     static_folder = static_path.joinpath('static')
152     static_folder.mkdir(parents=True, exist_ok=True)
153 
154     # unzip process
155     for zip_path, dir_name in [[js_zip_path, 'js'], [css_zip_path, 'css']]:
156         temp_dir = static_path.joinpath(f'temp_{dir_name}')
157         temp_dir.mkdir(mode=0o777, exist_ok=True, parents=True)
158         ZipFile(zip_path).extractall(temp_dir)
159         files_path = static_path.joinpath('static', dir_name)
160         if temp_dir.joinpath('build', 'static', dir_name).is_dir():
161             shutil.move(temp_dir.joinpath('build', 'static', dir_name), files_path)
162             shutil.rmtree(temp_dir)
163         else:
164             shutil.move(temp_dir, files_path)
165 
166     ZipFile(media_zip_path).extractall(static_folder)
167 
168     os.remove(js_zip_path)
169     os.remove(css_zip_path)
170     os.remove(media_zip_path)
171 
172     with open(version_txt_path, 'wt') as f:
173         f.write(gui_version_lv.vstring)
174 
175     log.info(f'GUI version updated to {gui_version_lv.vstring}')
176     return True
177 
178 
179 def initialize_flask(config):
180     # Apparently there's a bug that causes the static path not to work if it's '/' -- https://github.com/pallets/flask/issues/3134, I think '' should achieve the same thing (???)
181     app = Flask(
182         __name__,
183         static_url_path='/static',
184         static_folder=os.path.join(config.paths['static'], 'static/')
185     )
186 
187     app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 60
188     app.config['SWAGGER_HOST'] = 'http://localhost:8000/mindsdb'
189     authorizations = {
190         'apikey': {
191             'type': 'apiKey',
192             'in': 'query',
193             'name': 'apikey'
194         }
195     }
196 
197     port = config['api']['http']['port']
198     host = config['api']['http']['host']
199     hosts = ['0.0.0.0', 'localhost', '127.0.0.1']
200     if host not in hosts:
201         hosts.append(host)
202     cors_origin_list = [f'http://{h}:{port}' for h in hosts]
203 
204     if 'MINDSDB_CORS_PORT' in os.environ:
205         ports = os.environ['MINDSDB_CORS_PORT'].strip('[]').split(',')
206         ports = [f'http://{host}:{p}' for p in ports]
207         cors_origin_list.extend(ports)
208 
209     CORS(app, resources={r"/*": {"origins": cors_origin_list}})
210 
211     api = Swagger_Api(
212         app,
213         authorizations=authorizations,
214         security=['apikey'],
215         url_prefix=':8000',
216         prefix='/api',
217         doc='/doc/'
218     )
219 
220     # NOTE rewrite it, that hotfix to see GUI link
221     log = logging.getLogger('mindsdb.http')
222     url = f'http://{host}:{port}/'
223     log.error(f' - GUI available at {url}')
224 
225     pid = os.getpid()
226     x = threading.Thread(target=_open_webbrowser, args=(url, pid, port), daemon=True)
227     x.start()
228 
229     return app, api
230 
231 
232 def initialize_interfaces(config, app):
233     app.default_store = DataStore(config)
234     app.mindsdb_native = MindsdbNative(config)
235     app.custom_models = CustomModels(config)
236     app.config_obj = config
237 
238 
239 def _open_webbrowser(url: str, pid: int, port: int):
240     """Open webbrowser with url when http service is started.
241 
242     If some error then do nothing.
243     """
244     logger = logging.getLogger('mindsdb.http')
245     try:
246         is_http_active = wait_func_is_true(func=is_pid_listen_port, timeout=10,
247                                            pid=pid, port=port)
248         if is_http_active:
249             webbrowser.open(url)
250     except Exception as e:
251         logger.error(f'Failed to open {url} in webbrowser with exception {e}')
252         logger.error(traceback.format_exc())
253
```

Path: `mindsdb/api/http/start.py`
Content:
```
1 import os
2 import logging
3 import multiprocessing
4 from pathlib import Path
5 
6 from werkzeug.exceptions import HTTPException
7 from waitress import serve
8 from flask import send_from_directory
9 
10 from mindsdb.api.http.namespaces.predictor import ns_conf as predictor_ns
11 from mindsdb.api.http.namespaces.datasource import ns_conf as datasource_ns
12 from mindsdb.api.http.namespaces.util import ns_conf as utils_ns
13 from mindsdb.api.http.namespaces.config import ns_conf as conf_ns
14 from mindsdb.api.http.initialize import initialize_flask, initialize_interfaces, initialize_static
15 from mindsdb.utilities.config import Config
16 from mindsdb.utilities.log import initialize_log
17 
18 
19 def start(config, verbose=False):
20     config = Config(config)
21     if verbose:
22         config['log']['level']['console'] = 'DEBUG'
23 
24     initialize_log(config, 'http', wrap_print=True)
25 
26     initialize_static(config)
27 
28     app, api = initialize_flask(config)
29     initialize_interfaces(config, app)
30 
31     static_root = Path(config.paths['static'])
32 
33     @app.route('/', defaults={'path': ''}, methods=['GET'])
34     @app.route('/<path:path>', methods=['GET'])
35     def root_index(path):
36         if path.startswith('api/'):
37             return {'message': 'wrong query'}, 400
38         if static_root.joinpath(path).is_file():
39             return send_from_directory(config.paths['static'], path)
40         else:
41             return send_from_directory(config.paths['static'], 'index.html')
42 
43     api.add_namespace(predictor_ns)
44     api.add_namespace(datasource_ns)
45     api.add_namespace(utils_ns)
46     api.add_namespace(conf_ns)
47 
48     @api.errorhandler(Exception)
49     def handle_exception(e):
50         # pass through HTTP errors
51         if isinstance(e, HTTPException):
52             return {'message': str(e)}, e.code, e.get_response().headers
53         name = getattr(type(e), '__name__') or 'Unknown error'
54         return {'message': f'{name}: {str(e)}'}, 500
55 
56     port = config['api']['http']['port']
57     host = config['api']['http']['host']
58 
59     server = os.environ.get('MINDSDB_DEFAULT_SERVER', 'waitress')
60 
61     if server.lower() == 'waitress':
62         serve(app, port=port, host=host)
63     elif server.lower() == 'flask':
64         # that will 'disable access' log in console
65         log = logging.getLogger('werkzeug')
66         log.setLevel(logging.WARNING)
67 
68         app.run(debug=False, port=port, host=host)
69     elif server.lower() == 'gunicorn':
70         try:
71             from mindsdb.api.http.gunicorn_wrapper import StandaloneApplication
72         except ImportError:
73             print("Gunicorn server is not available by default. If you wish to use it, please install 'gunicorn'")
74             return
75 
76         options = {
77             'bind': f'{host}:{port}',
78             'workers': min(max(multiprocessing.cpu_count(), 2), 3)
79         }
80         StandaloneApplication(app, options).run()
81
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/mindsdb/api/http/initialize.py b/mindsdb/api/http/initialize.py
--- a/mindsdb/api/http/initialize.py
+++ b/mindsdb/api/http/initialize.py
@@ -8,6 +8,7 @@
 from pathlib import Path
 import logging
 import traceback
+import concurrent.futures
 
 from flask import Flask, url_for
 from flask_restx import Api
@@ -138,11 +139,23 @@
             }
         ]
 
-        for r in resources:
-            response = requests.get(r['url'])
-            if response.status_code != 200:
-                raise Exception(f"Error {response.status_code} GET {r['url']}")
-            open(r['path'], 'wb').write(response.content)
+        def get_resources(resource):
+            try:
+                response = requests.get(resource['url'])
+                if response.status_code != requests.status_codes.codes.ok:
+                    return Exception(f"Error {response.status_code} GET {resource['url']}")
+                open(resource['path'], 'wb').write(response.content)
+            except Exception as e:
+                return e
+            return None
+
+        # to make downloading faster download each resource in a separate thread
+        with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
+            future_to_url = {executor.submit(get_resources, r): r for r in resources}
+            for future in concurrent.futures.as_completed(future_to_url):
+                res = future.result()
+                if res is not None:
+                    raise res
 
     except Exception as e:
         log.error(f'Error during downloading files from s3: {e}')
diff --git a/mindsdb/api/http/start.py b/mindsdb/api/http/start.py
--- a/mindsdb/api/http/start.py
+++ b/mindsdb/api/http/start.py
@@ -1,6 +1,7 @@
 import os
 import logging
 import multiprocessing
+import threading
 from pathlib import Path
 
 from werkzeug.exceptions import HTTPException
@@ -23,7 +24,9 @@
 
     initialize_log(config, 'http', wrap_print=True)
 
-    initialize_static(config)
+    # start static initialization in a separate thread
+    init_static_thread = threading.Thread(target=initialize_static, args=(config,))
+    init_static_thread.start()
 
     app, api = initialize_flask(config)
     initialize_interfaces(config, app)
@@ -58,6 +61,8 @@
 
     server = os.environ.get('MINDSDB_DEFAULT_SERVER', 'waitress')
 
+    # waiting static initialization
+    init_static_thread.join()
     if server.lower() == 'waitress':
         serve(app, port=port, host=host)
     elif server.lower() == 'flask':

{"golden_diff": "diff --git a/mindsdb/api/http/initialize.py b/mindsdb/api/http/initialize.py\n--- a/mindsdb/api/http/initialize.py\n+++ b/mindsdb/api/http/initialize.py\n@@ -8,6 +8,7 @@\n from pathlib import Path\n import logging\n import traceback\n+import concurrent.futures\n \n from flask import Flask, url_for\n from flask_restx import Api\n@@ -138,11 +139,23 @@\n             }\n         ]\n \n-        for r in resources:\n-            response = requests.get(r['url'])\n-            if response.status_code != 200:\n-                raise Exception(f\"Error {response.status_code} GET {r['url']}\")\n-            open(r['path'], 'wb').write(response.content)\n+        def get_resources(resource):\n+            try:\n+                response = requests.get(resource['url'])\n+                if response.status_code != requests.status_codes.codes.ok:\n+                    return Exception(f\"Error {response.status_code} GET {resource['url']}\")\n+                open(resource['path'], 'wb').write(response.content)\n+            except Exception as e:\n+                return e\n+            return None\n+\n+        # to make downloading faster download each resource in a separate thread\n+        with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:\n+            future_to_url = {executor.submit(get_resources, r): r for r in resources}\n+            for future in concurrent.futures.as_completed(future_to_url):\n+                res = future.result()\n+                if res is not None:\n+                    raise res\n \n     except Exception as e:\n         log.error(f'Error during downloading files from s3: {e}')\ndiff --git a/mindsdb/api/http/start.py b/mindsdb/api/http/start.py\n--- a/mindsdb/api/http/start.py\n+++ b/mindsdb/api/http/start.py\n@@ -1,6 +1,7 @@\n import os\n import logging\n import multiprocessing\n+import threading\n from pathlib import Path\n \n from werkzeug.exceptions import HTTPException\n@@ -23,7 +24,9 @@\n \n     initialize_log(config, 'http', wrap_print=True)\n \n-    initialize_static(config)\n+    # start static initialization in a separate thread\n+    init_static_thread = threading.Thread(target=initialize_static, args=(config,))\n+    init_static_thread.start()\n \n     app, api = initialize_flask(config)\n     initialize_interfaces(config, app)\n@@ -58,6 +61,8 @@\n \n     server = os.environ.get('MINDSDB_DEFAULT_SERVER', 'waitress')\n \n+    # waiting static initialization\n+    init_static_thread.join()\n     if server.lower() == 'waitress':\n         serve(app, port=port, host=host)\n     elif server.lower() == 'flask':\n", "issue": "Async download Scout on start\nWhen mindsdb-http api starts, it checks new version of gui is available. If it available, mindsdb starts download it. Issue here - is mindsdb-http server is not start until gui-downloading finishes. Because of this launch time of http-server can be long.\r\nTo fix it, need run initialize_static (https://github.com/mindsdb/mindsdb/blob/staging/mindsdb/api/http/start.py#L24) function in separate process/thread.\r\nAlso, it will be good to make concurrent download of GUI files here: https://github.com/mindsdb/mindsdb/blob/staging/mindsdb/api/http/initialize.py#L141\n", "before_files": [{"content": "from distutils.version import LooseVersion\nimport requests\nimport os\nimport shutil\nimport threading\nimport webbrowser\nfrom zipfile import ZipFile\nfrom pathlib import Path\nimport logging\nimport traceback\n\nfrom flask import Flask, url_for\nfrom flask_restx import Api\nfrom flask_cors import CORS\n\nfrom mindsdb.__about__ import __version__ as mindsdb_version\nfrom mindsdb.interfaces.datastore.datastore import DataStore\nfrom mindsdb.interfaces.native.mindsdb import MindsdbNative\nfrom mindsdb.interfaces.custom.custom_models import CustomModels\nfrom mindsdb.utilities.ps import is_pid_listen_port, wait_func_is_true\n\n\nclass Swagger_Api(Api):\n    \"\"\"\n    This is a modification of the base Flask Restplus Api class due to the issue described here\n    https://github.com/noirbizarre/flask-restplus/issues/223\n    \"\"\"\n    @property\n    def specs_url(self):\n        return url_for(self.endpoint(\"specs\"), _external=False)\n\n\ndef initialize_static(config):\n    ''' Update Scout files basing on compatible-config.json content.\n        Files will be downloaded and updated if new version of GUI > current.\n        Current GUI version stored in static/version.txt.\n    '''\n    log = logging.getLogger('mindsdb.http')\n    static_path = Path(config.paths['static'])\n    static_path.mkdir(parents=True, exist_ok=True)\n\n    try:\n        res = requests.get('https://mindsdb-web-builds.s3.amazonaws.com/compatible-config.json')\n    except (ConnectionError, requests.exceptions.ConnectionError) as e:\n        print(f'Is no connection. {e}')\n        return False\n    except Exception as e:\n        print(f'Is something wrong with getting compatible-config.json: {e}')\n        return False\n\n    if res.status_code != 200:\n        print(f'Cant get compatible-config.json: returned status code = {res.status_code}')\n        return False\n\n    try:\n        versions = res.json()\n    except Exception as e:\n        print(f'Cant decode compatible-config.json: {e}')\n        return False\n\n    current_mindsdb_lv = LooseVersion(mindsdb_version)\n\n    try:\n        gui_versions = {}\n        max_mindsdb_lv = None\n        max_gui_lv = None\n        for el in versions['mindsdb']:\n            if el['mindsdb_version'] is None:\n                gui_lv = LooseVersion(el['gui_version'])\n            else:\n                mindsdb_lv = LooseVersion(el['mindsdb_version'])\n                gui_lv = LooseVersion(el['gui_version'])\n                if mindsdb_lv.vstring not in gui_versions or gui_lv > gui_versions[mindsdb_lv.vstring]:\n                    gui_versions[mindsdb_lv.vstring] = gui_lv\n                if max_mindsdb_lv is None or max_mindsdb_lv < mindsdb_lv:\n                    max_mindsdb_lv = mindsdb_lv\n            if max_gui_lv is None or max_gui_lv < gui_lv:\n                max_gui_lv = gui_lv\n\n        all_mindsdb_lv = [LooseVersion(x) for x in gui_versions.keys()]\n        all_mindsdb_lv.sort()\n\n        if current_mindsdb_lv.vstring in gui_versions:\n            gui_version_lv = gui_versions[current_mindsdb_lv.vstring]\n        elif current_mindsdb_lv > all_mindsdb_lv[-1]:\n            gui_version_lv = max_gui_lv\n        else:\n            lower_versions = {key: value for key, value in gui_versions.items() if LooseVersion(key) < current_mindsdb_lv}\n            if len(lower_versions) == 0:\n                gui_version_lv = gui_versions[all_mindsdb_lv[0].vstring]\n            else:\n                all_lower_versions = [LooseVersion(x) for x in lower_versions.keys()]\n                gui_version_lv = gui_versions[all_lower_versions[-1].vstring]\n    except Exception as e:\n        log.error(f'Error in compatible-config.json structure: {e}')\n        return False\n\n    current_gui_version = None\n\n    version_txt_path = static_path.joinpath('version.txt')\n    if version_txt_path.is_file():\n        with open(version_txt_path, 'rt') as f:\n            current_gui_version = f.readline()\n    if current_gui_version is not None:\n        current_gui_lv = LooseVersion(current_gui_version)\n        if current_gui_lv >= gui_version_lv:\n            return True\n\n    log.info(f'New version of GUI available ({gui_version_lv.vstring}). Downloading...')\n\n    shutil.rmtree(static_path)\n    static_path.mkdir(parents=True, exist_ok=True)\n\n    try:\n        css_zip_path = str(static_path.joinpath('css.zip'))\n        js_zip_path = str(static_path.joinpath('js.zip'))\n        media_zip_path = str(static_path.joinpath('media.zip'))\n        bucket = \"https://mindsdb-web-builds.s3.amazonaws.com/\"\n\n        gui_version = gui_version_lv.vstring\n\n        resources = [\n            {\n                'url': bucket + 'css-V' + gui_version + '.zip',\n                'path': css_zip_path\n            }, {\n                'url': bucket + 'js-V' + gui_version + '.zip',\n                'path': js_zip_path\n            }, {\n                'url': bucket + 'indexV' + gui_version + '.html',\n                'path': str(static_path.joinpath('index.html'))\n            }, {\n                'url': bucket + 'favicon.ico',\n                'path': str(static_path.joinpath('favicon.ico'))\n            }, {\n                'url': bucket + 'media.zip',\n                'path': media_zip_path\n            }\n        ]\n\n        for r in resources:\n            response = requests.get(r['url'])\n            if response.status_code != 200:\n                raise Exception(f\"Error {response.status_code} GET {r['url']}\")\n            open(r['path'], 'wb').write(response.content)\n\n    except Exception as e:\n        log.error(f'Error during downloading files from s3: {e}')\n        return False\n\n    static_folder = static_path.joinpath('static')\n    static_folder.mkdir(parents=True, exist_ok=True)\n\n    # unzip process\n    for zip_path, dir_name in [[js_zip_path, 'js'], [css_zip_path, 'css']]:\n        temp_dir = static_path.joinpath(f'temp_{dir_name}')\n        temp_dir.mkdir(mode=0o777, exist_ok=True, parents=True)\n        ZipFile(zip_path).extractall(temp_dir)\n        files_path = static_path.joinpath('static', dir_name)\n        if temp_dir.joinpath('build', 'static', dir_name).is_dir():\n            shutil.move(temp_dir.joinpath('build', 'static', dir_name), files_path)\n            shutil.rmtree(temp_dir)\n        else:\n            shutil.move(temp_dir, files_path)\n\n    ZipFile(media_zip_path).extractall(static_folder)\n\n    os.remove(js_zip_path)\n    os.remove(css_zip_path)\n    os.remove(media_zip_path)\n\n    with open(version_txt_path, 'wt') as f:\n        f.write(gui_version_lv.vstring)\n\n    log.info(f'GUI version updated to {gui_version_lv.vstring}')\n    return True\n\n\ndef initialize_flask(config):\n    # Apparently there's a bug that causes the static path not to work if it's '/' -- https://github.com/pallets/flask/issues/3134, I think '' should achieve the same thing (???)\n    app = Flask(\n        __name__,\n        static_url_path='/static',\n        static_folder=os.path.join(config.paths['static'], 'static/')\n    )\n\n    app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 60\n    app.config['SWAGGER_HOST'] = 'http://localhost:8000/mindsdb'\n    authorizations = {\n        'apikey': {\n            'type': 'apiKey',\n            'in': 'query',\n            'name': 'apikey'\n        }\n    }\n\n    port = config['api']['http']['port']\n    host = config['api']['http']['host']\n    hosts = ['0.0.0.0', 'localhost', '127.0.0.1']\n    if host not in hosts:\n        hosts.append(host)\n    cors_origin_list = [f'http://{h}:{port}' for h in hosts]\n\n    if 'MINDSDB_CORS_PORT' in os.environ:\n        ports = os.environ['MINDSDB_CORS_PORT'].strip('[]').split(',')\n        ports = [f'http://{host}:{p}' for p in ports]\n        cors_origin_list.extend(ports)\n\n    CORS(app, resources={r\"/*\": {\"origins\": cors_origin_list}})\n\n    api = Swagger_Api(\n        app,\n        authorizations=authorizations,\n        security=['apikey'],\n        url_prefix=':8000',\n        prefix='/api',\n        doc='/doc/'\n    )\n\n    # NOTE rewrite it, that hotfix to see GUI link\n    log = logging.getLogger('mindsdb.http')\n    url = f'http://{host}:{port}/'\n    log.error(f' - GUI available at {url}')\n\n    pid = os.getpid()\n    x = threading.Thread(target=_open_webbrowser, args=(url, pid, port), daemon=True)\n    x.start()\n\n    return app, api\n\n\ndef initialize_interfaces(config, app):\n    app.default_store = DataStore(config)\n    app.mindsdb_native = MindsdbNative(config)\n    app.custom_models = CustomModels(config)\n    app.config_obj = config\n\n\ndef _open_webbrowser(url: str, pid: int, port: int):\n    \"\"\"Open webbrowser with url when http service is started.\n\n    If some error then do nothing.\n    \"\"\"\n    logger = logging.getLogger('mindsdb.http')\n    try:\n        is_http_active = wait_func_is_true(func=is_pid_listen_port, timeout=10,\n                                           pid=pid, port=port)\n        if is_http_active:\n            webbrowser.open(url)\n    except Exception as e:\n        logger.error(f'Failed to open {url} in webbrowser with exception {e}')\n        logger.error(traceback.format_exc())\n", "path": "mindsdb/api/http/initialize.py"}, {"content": "import os\nimport logging\nimport multiprocessing\nfrom pathlib import Path\n\nfrom werkzeug.exceptions import HTTPException\nfrom waitress import serve\nfrom flask import send_from_directory\n\nfrom mindsdb.api.http.namespaces.predictor import ns_conf as predictor_ns\nfrom mindsdb.api.http.namespaces.datasource import ns_conf as datasource_ns\nfrom mindsdb.api.http.namespaces.util import ns_conf as utils_ns\nfrom mindsdb.api.http.namespaces.config import ns_conf as conf_ns\nfrom mindsdb.api.http.initialize import initialize_flask, initialize_interfaces, initialize_static\nfrom mindsdb.utilities.config import Config\nfrom mindsdb.utilities.log import initialize_log\n\n\ndef start(config, verbose=False):\n    config = Config(config)\n    if verbose:\n        config['log']['level']['console'] = 'DEBUG'\n\n    initialize_log(config, 'http', wrap_print=True)\n\n    initialize_static(config)\n\n    app, api = initialize_flask(config)\n    initialize_interfaces(config, app)\n\n    static_root = Path(config.paths['static'])\n\n    @app.route('/', defaults={'path': ''}, methods=['GET'])\n    @app.route('/<path:path>', methods=['GET'])\n    def root_index(path):\n        if path.startswith('api/'):\n            return {'message': 'wrong query'}, 400\n        if static_root.joinpath(path).is_file():\n            return send_from_directory(config.paths['static'], path)\n        else:\n            return send_from_directory(config.paths['static'], 'index.html')\n\n    api.add_namespace(predictor_ns)\n    api.add_namespace(datasource_ns)\n    api.add_namespace(utils_ns)\n    api.add_namespace(conf_ns)\n\n    @api.errorhandler(Exception)\n    def handle_exception(e):\n        # pass through HTTP errors\n        if isinstance(e, HTTPException):\n            return {'message': str(e)}, e.code, e.get_response().headers\n        name = getattr(type(e), '__name__') or 'Unknown error'\n        return {'message': f'{name}: {str(e)}'}, 500\n\n    port = config['api']['http']['port']\n    host = config['api']['http']['host']\n\n    server = os.environ.get('MINDSDB_DEFAULT_SERVER', 'waitress')\n\n    if server.lower() == 'waitress':\n        serve(app, port=port, host=host)\n    elif server.lower() == 'flask':\n        # that will 'disable access' log in console\n        log = logging.getLogger('werkzeug')\n        log.setLevel(logging.WARNING)\n\n        app.run(debug=False, port=port, host=host)\n    elif server.lower() == 'gunicorn':\n        try:\n            from mindsdb.api.http.gunicorn_wrapper import StandaloneApplication\n        except ImportError:\n            print(\"Gunicorn server is not available by default. If you wish to use it, please install 'gunicorn'\")\n            return\n\n        options = {\n            'bind': f'{host}:{port}',\n            'workers': min(max(multiprocessing.cpu_count(), 2), 3)\n        }\n        StandaloneApplication(app, options).run()\n", "path": "mindsdb/api/http/start.py"}], "after_files": [{"content": "from distutils.version import LooseVersion\nimport requests\nimport os\nimport shutil\nimport threading\nimport webbrowser\nfrom zipfile import ZipFile\nfrom pathlib import Path\nimport logging\nimport traceback\nimport concurrent.futures\n\nfrom flask import Flask, url_for\nfrom flask_restx import Api\nfrom flask_cors import CORS\n\nfrom mindsdb.__about__ import __version__ as mindsdb_version\nfrom mindsdb.interfaces.datastore.datastore import DataStore\nfrom mindsdb.interfaces.native.mindsdb import MindsdbNative\nfrom mindsdb.interfaces.custom.custom_models import CustomModels\nfrom mindsdb.utilities.ps import is_pid_listen_port, wait_func_is_true\n\n\nclass Swagger_Api(Api):\n    \"\"\"\n    This is a modification of the base Flask Restplus Api class due to the issue described here\n    https://github.com/noirbizarre/flask-restplus/issues/223\n    \"\"\"\n    @property\n    def specs_url(self):\n        return url_for(self.endpoint(\"specs\"), _external=False)\n\n\ndef initialize_static(config):\n    ''' Update Scout files basing on compatible-config.json content.\n        Files will be downloaded and updated if new version of GUI > current.\n        Current GUI version stored in static/version.txt.\n    '''\n    log = logging.getLogger('mindsdb.http')\n    static_path = Path(config.paths['static'])\n    static_path.mkdir(parents=True, exist_ok=True)\n\n    try:\n        res = requests.get('https://mindsdb-web-builds.s3.amazonaws.com/compatible-config.json')\n    except (ConnectionError, requests.exceptions.ConnectionError) as e:\n        print(f'Is no connection. {e}')\n        return False\n    except Exception as e:\n        print(f'Is something wrong with getting compatible-config.json: {e}')\n        return False\n\n    if res.status_code != 200:\n        print(f'Cant get compatible-config.json: returned status code = {res.status_code}')\n        return False\n\n    try:\n        versions = res.json()\n    except Exception as e:\n        print(f'Cant decode compatible-config.json: {e}')\n        return False\n\n    current_mindsdb_lv = LooseVersion(mindsdb_version)\n\n    try:\n        gui_versions = {}\n        max_mindsdb_lv = None\n        max_gui_lv = None\n        for el in versions['mindsdb']:\n            if el['mindsdb_version'] is None:\n                gui_lv = LooseVersion(el['gui_version'])\n            else:\n                mindsdb_lv = LooseVersion(el['mindsdb_version'])\n                gui_lv = LooseVersion(el['gui_version'])\n                if mindsdb_lv.vstring not in gui_versions or gui_lv > gui_versions[mindsdb_lv.vstring]:\n                    gui_versions[mindsdb_lv.vstring] = gui_lv\n                if max_mindsdb_lv is None or max_mindsdb_lv < mindsdb_lv:\n                    max_mindsdb_lv = mindsdb_lv\n            if max_gui_lv is None or max_gui_lv < gui_lv:\n                max_gui_lv = gui_lv\n\n        all_mindsdb_lv = [LooseVersion(x) for x in gui_versions.keys()]\n        all_mindsdb_lv.sort()\n\n        if current_mindsdb_lv.vstring in gui_versions:\n            gui_version_lv = gui_versions[current_mindsdb_lv.vstring]\n        elif current_mindsdb_lv > all_mindsdb_lv[-1]:\n            gui_version_lv = max_gui_lv\n        else:\n            lower_versions = {key: value for key, value in gui_versions.items() if LooseVersion(key) < current_mindsdb_lv}\n            if len(lower_versions) == 0:\n                gui_version_lv = gui_versions[all_mindsdb_lv[0].vstring]\n            else:\n                all_lower_versions = [LooseVersion(x) for x in lower_versions.keys()]\n                gui_version_lv = gui_versions[all_lower_versions[-1].vstring]\n    except Exception as e:\n        log.error(f'Error in compatible-config.json structure: {e}')\n        return False\n\n    current_gui_version = None\n\n    version_txt_path = static_path.joinpath('version.txt')\n    if version_txt_path.is_file():\n        with open(version_txt_path, 'rt') as f:\n            current_gui_version = f.readline()\n    if current_gui_version is not None:\n        current_gui_lv = LooseVersion(current_gui_version)\n        if current_gui_lv >= gui_version_lv:\n            return True\n\n    log.info(f'New version of GUI available ({gui_version_lv.vstring}). Downloading...')\n\n    shutil.rmtree(static_path)\n    static_path.mkdir(parents=True, exist_ok=True)\n\n    try:\n        css_zip_path = str(static_path.joinpath('css.zip'))\n        js_zip_path = str(static_path.joinpath('js.zip'))\n        media_zip_path = str(static_path.joinpath('media.zip'))\n        bucket = \"https://mindsdb-web-builds.s3.amazonaws.com/\"\n\n        gui_version = gui_version_lv.vstring\n\n        resources = [\n            {\n                'url': bucket + 'css-V' + gui_version + '.zip',\n                'path': css_zip_path\n            }, {\n                'url': bucket + 'js-V' + gui_version + '.zip',\n                'path': js_zip_path\n            }, {\n                'url': bucket + 'indexV' + gui_version + '.html',\n                'path': str(static_path.joinpath('index.html'))\n            }, {\n                'url': bucket + 'favicon.ico',\n                'path': str(static_path.joinpath('favicon.ico'))\n            }, {\n                'url': bucket + 'media.zip',\n                'path': media_zip_path\n            }\n        ]\n\n        def get_resources(resource):\n            try:\n                response = requests.get(resource['url'])\n                if response.status_code != requests.status_codes.codes.ok:\n                    return Exception(f\"Error {response.status_code} GET {resource['url']}\")\n                open(resource['path'], 'wb').write(response.content)\n            except Exception as e:\n                return e\n            return None\n\n        # to make downloading faster download each resource in a separate thread\n        with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:\n            future_to_url = {executor.submit(get_resources, r): r for r in resources}\n            for future in concurrent.futures.as_completed(future_to_url):\n                res = future.result()\n                if res is not None:\n                    raise res\n\n    except Exception as e:\n        log.error(f'Error during downloading files from s3: {e}')\n        return False\n\n    static_folder = static_path.joinpath('static')\n    static_folder.mkdir(parents=True, exist_ok=True)\n\n    # unzip process\n    for zip_path, dir_name in [[js_zip_path, 'js'], [css_zip_path, 'css']]:\n        temp_dir = static_path.joinpath(f'temp_{dir_name}')\n        temp_dir.mkdir(mode=0o777, exist_ok=True, parents=True)\n        ZipFile(zip_path).extractall(temp_dir)\n        files_path = static_path.joinpath('static', dir_name)\n        if temp_dir.joinpath('build', 'static', dir_name).is_dir():\n            shutil.move(temp_dir.joinpath('build', 'static', dir_name), files_path)\n            shutil.rmtree(temp_dir)\n        else:\n            shutil.move(temp_dir, files_path)\n\n    ZipFile(media_zip_path).extractall(static_folder)\n\n    os.remove(js_zip_path)\n    os.remove(css_zip_path)\n    os.remove(media_zip_path)\n\n    with open(version_txt_path, 'wt') as f:\n        f.write(gui_version_lv.vstring)\n\n    log.info(f'GUI version updated to {gui_version_lv.vstring}')\n    return True\n\n\ndef initialize_flask(config):\n    # Apparently there's a bug that causes the static path not to work if it's '/' -- https://github.com/pallets/flask/issues/3134, I think '' should achieve the same thing (???)\n    app = Flask(\n        __name__,\n        static_url_path='/static',\n        static_folder=os.path.join(config.paths['static'], 'static/')\n    )\n\n    app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 60\n    app.config['SWAGGER_HOST'] = 'http://localhost:8000/mindsdb'\n    authorizations = {\n        'apikey': {\n            'type': 'apiKey',\n            'in': 'query',\n            'name': 'apikey'\n        }\n    }\n\n    port = config['api']['http']['port']\n    host = config['api']['http']['host']\n    hosts = ['0.0.0.0', 'localhost', '127.0.0.1']\n    if host not in hosts:\n        hosts.append(host)\n    cors_origin_list = [f'http://{h}:{port}' for h in hosts]\n\n    if 'MINDSDB_CORS_PORT' in os.environ:\n        ports = os.environ['MINDSDB_CORS_PORT'].strip('[]').split(',')\n        ports = [f'http://{host}:{p}' for p in ports]\n        cors_origin_list.extend(ports)\n\n    CORS(app, resources={r\"/*\": {\"origins\": cors_origin_list}})\n\n    api = Swagger_Api(\n        app,\n        authorizations=authorizations,\n        security=['apikey'],\n        url_prefix=':8000',\n        prefix='/api',\n        doc='/doc/'\n    )\n\n    # NOTE rewrite it, that hotfix to see GUI link\n    log = logging.getLogger('mindsdb.http')\n    url = f'http://{host}:{port}/'\n    log.error(f' - GUI available at {url}')\n\n    pid = os.getpid()\n    x = threading.Thread(target=_open_webbrowser, args=(url, pid, port), daemon=True)\n    x.start()\n\n    return app, api\n\n\ndef initialize_interfaces(config, app):\n    app.default_store = DataStore(config)\n    app.mindsdb_native = MindsdbNative(config)\n    app.custom_models = CustomModels(config)\n    app.config_obj = config\n\n\ndef _open_webbrowser(url: str, pid: int, port: int):\n    \"\"\"Open webbrowser with url when http service is started.\n\n    If some error then do nothing.\n    \"\"\"\n    logger = logging.getLogger('mindsdb.http')\n    try:\n        is_http_active = wait_func_is_true(func=is_pid_listen_port, timeout=10,\n                                           pid=pid, port=port)\n        if is_http_active:\n            webbrowser.open(url)\n    except Exception as e:\n        logger.error(f'Failed to open {url} in webbrowser with exception {e}')\n        logger.error(traceback.format_exc())\n", "path": "mindsdb/api/http/initialize.py"}, {"content": "import os\nimport logging\nimport multiprocessing\nimport threading\nfrom pathlib import Path\n\nfrom werkzeug.exceptions import HTTPException\nfrom waitress import serve\nfrom flask import send_from_directory\n\nfrom mindsdb.api.http.namespaces.predictor import ns_conf as predictor_ns\nfrom mindsdb.api.http.namespaces.datasource import ns_conf as datasource_ns\nfrom mindsdb.api.http.namespaces.util import ns_conf as utils_ns\nfrom mindsdb.api.http.namespaces.config import ns_conf as conf_ns\nfrom mindsdb.api.http.initialize import initialize_flask, initialize_interfaces, initialize_static\nfrom mindsdb.utilities.config import Config\nfrom mindsdb.utilities.log import initialize_log\n\n\ndef start(config, verbose=False):\n    config = Config(config)\n    if verbose:\n        config['log']['level']['console'] = 'DEBUG'\n\n    initialize_log(config, 'http', wrap_print=True)\n\n    # start static initialization in a separate thread\n    init_static_thread = threading.Thread(target=initialize_static, args=(config,))\n    init_static_thread.start()\n\n    app, api = initialize_flask(config)\n    initialize_interfaces(config, app)\n\n    static_root = Path(config.paths['static'])\n\n    @app.route('/', defaults={'path': ''}, methods=['GET'])\n    @app.route('/<path:path>', methods=['GET'])\n    def root_index(path):\n        if path.startswith('api/'):\n            return {'message': 'wrong query'}, 400\n        if static_root.joinpath(path).is_file():\n            return send_from_directory(config.paths['static'], path)\n        else:\n            return send_from_directory(config.paths['static'], 'index.html')\n\n    api.add_namespace(predictor_ns)\n    api.add_namespace(datasource_ns)\n    api.add_namespace(utils_ns)\n    api.add_namespace(conf_ns)\n\n    @api.errorhandler(Exception)\n    def handle_exception(e):\n        # pass through HTTP errors\n        if isinstance(e, HTTPException):\n            return {'message': str(e)}, e.code, e.get_response().headers\n        name = getattr(type(e), '__name__') or 'Unknown error'\n        return {'message': f'{name}: {str(e)}'}, 500\n\n    port = config['api']['http']['port']\n    host = config['api']['http']['host']\n\n    server = os.environ.get('MINDSDB_DEFAULT_SERVER', 'waitress')\n\n    # waiting static initialization\n    init_static_thread.join()\n    if server.lower() == 'waitress':\n        serve(app, port=port, host=host)\n    elif server.lower() == 'flask':\n        # that will 'disable access' log in console\n        log = logging.getLogger('werkzeug')\n        log.setLevel(logging.WARNING)\n\n        app.run(debug=False, port=port, host=host)\n    elif server.lower() == 'gunicorn':\n        try:\n            from mindsdb.api.http.gunicorn_wrapper import StandaloneApplication\n        except ImportError:\n            print(\"Gunicorn server is not available by default. If you wish to use it, please install 'gunicorn'\")\n            return\n\n        options = {\n            'bind': f'{host}:{port}',\n            'workers': min(max(multiprocessing.cpu_count(), 2), 3)\n        }\n        StandaloneApplication(app, options).run()\n", "path": "mindsdb/api/http/start.py"}]}