Engine downloader API for #31 (#42)

* Add is_engine_available_to_download API call

* Fix issue with worker never throwing error if engine is not found

* Add API call to get most recent engine version

* Fix some minor import issues

* Fix web urls

* Fix default server log level

* Add progress bar for project download worker_factory downloads missing engine versions

* Better error handling when invalid version is given

* Add timeouts to engine downloaders
This commit is contained in:
2023-10-22 15:02:30 -07:00
committed by GitHub
parent 9603046432
commit e52682c8b9
9 changed files with 193 additions and 68 deletions

View File

@@ -12,24 +12,25 @@ import threading
import time
import zipfile
from datetime import datetime
from urllib.request import urlretrieve
from zipfile import ZipFile
import json2html
import psutil
import requests
import yaml
from flask import Flask, request, render_template, send_file, after_this_request, Response, redirect, url_for, abort
from tqdm import tqdm
from werkzeug.utils import secure_filename
from src.api.server_proxy import RenderServerProxy
from src.distributed_job_manager import DistributedJobManager
from src.engines.core.base_worker import string_to_status, RenderStatus
from src.engines.core.worker_factory import RenderWorkerFactory
from src.engines.engine_manager import EngineManager
from src.render_queue import RenderQueue, JobNotFoundError
from src.api.server_proxy import RenderServerProxy
from src.utilities.misc_helper import system_safe_path
from src.utilities.server_helper import generate_thumbnail_for_job
from src.utilities.zeroconf_server import ZeroconfServer
from src.utilities.misc_helper import system_safe_path
from src.engines.core.worker_factory import RenderWorkerFactory
from src.engines.core.base_worker import string_to_status, RenderStatus
logger = logging.getLogger()
server = Flask(__name__, template_folder='web/templates', static_folder='web/static')
@@ -133,15 +134,15 @@ def job_thumbnail(job_id):
# Misc status icons
if found_job.status == RenderStatus.RUNNING:
return send_file('web/static/images/gears.png', mimetype="image/png")
return send_file('../web/static/images/gears.png', mimetype="image/png")
elif found_job.status == RenderStatus.CANCELLED:
return send_file('web/static/images/cancelled.png', mimetype="image/png")
return send_file('../web/static/images/cancelled.png', mimetype="image/png")
elif found_job.status == RenderStatus.SCHEDULED:
return send_file('web/static/images/scheduled.png', mimetype="image/png")
return send_file('../web/static/images/scheduled.png', mimetype="image/png")
elif found_job.status == RenderStatus.NOT_STARTED:
return send_file('web/static/images/not_started.png', mimetype="image/png")
return send_file('../web/static/images/not_started.png', mimetype="image/png")
# errors
return send_file('web/static/images/error.png', mimetype="image/png")
return send_file('../web/static/images/error.png', mimetype="image/png")
# Get job file routing
@@ -190,7 +191,7 @@ def get_job_status(job_id):
@server.get('/api/job/<job_id>/logs')
def get_job_logs(job_id):
found_job = RenderQueue.job_with_id(job_id)
log_path = system_safe_path(found_job.log_path()),
log_path = system_safe_path(found_job.log_path())
log_data = None
if log_path and os.path.exists(log_path):
with open(log_path) as file:
@@ -322,10 +323,30 @@ def add_job_handler():
referred_name = os.path.basename(uploaded_project.filename)
elif project_url:
# download and save url - have to download first to know filename due to redirects
logger.info(f"Attempting to download URL: {project_url}")
logger.info(f"Downloading project from url: {project_url}")
try:
downloaded_file_url, info = urlretrieve(project_url)
referred_name = info.get_filename() or os.path.basename(project_url)
referred_name = os.path.basename(project_url)
response = requests.get(project_url, stream=True)
if response.status_code == 200:
# Get the total file size from the "Content-Length" header
file_size = int(response.headers.get("Content-Length", 0))
# Create a progress bar using tqdm
progress_bar = tqdm(total=file_size, unit="B", unit_scale=True)
# Open a file for writing in binary mode
downloaded_file_url = os.path.join(tempfile.gettempdir(), referred_name)
with open(downloaded_file_url, "wb") as file:
for chunk in response.iter_content(chunk_size=1024):
if chunk:
# Write the chunk to the file
file.write(chunk)
# Update the progress bar
progress_bar.update(len(chunk))
# Close the progress bar
progress_bar.close()
except Exception as e:
err_msg = f"Error downloading file: {e}"
logger.error(err_msg)
@@ -419,6 +440,10 @@ def add_job_handler():
if not worker.parent:
make_job_ready(worker.id)
results.append(worker.json())
except FileNotFoundError as e:
err_msg = f"Cannot create job: {e}"
logger.error(err_msg)
results.append({'error': err_msg})
except Exception as e:
err_msg = f"Exception creating render job: {e}"
logger.exception(err_msg)
@@ -549,6 +574,24 @@ def renderer_info():
'supported_export_formats': engine(install_path).get_output_formats()}
return renderer_data
@server.get('/api/is_engine_available_to_download')
def is_engine_available_to_download():
available_result = EngineManager.version_is_available_to_download(request.args.get('engine'),
request.args.get('version'),
request.args.get('system_os'),
request.args.get('cpu'))
return available_result if available_result else \
(f"Cannot find available download for {request.args.get('engine')} {request.args.get('version')}", 500)
@server.get('/api/find_most_recent_version')
def find_most_recent_version():
most_recent = EngineManager.find_most_recent_version(request.args.get('engine'),
request.args.get('system_os'),
request.args.get('cpu'))
return most_recent if most_recent else \
(f"Error finding most recent version of {request.args.get('engine')}", 500)
@server.post('/api/download_engine')
def download_engine():
@@ -556,7 +599,8 @@ def download_engine():
request.args.get('version'),
request.args.get('system_os'),
request.args.get('cpu'))
return download_result if download_result else ("Error downloading requested engine", 500)
return download_result if download_result else \
(f"Error downloading {request.args.get('engine')} {request.args.get('version')}", 500)
@server.post('/api/delete_engine')
@@ -565,7 +609,8 @@ def delete_engine_download():
request.args.get('version'),
request.args.get('system_os'),
request.args.get('cpu'))
return "Success" if delete_result else ("Error deleting requested engine", 500)
return "Success" if delete_result else \
(f"Error deleting {request.args.get('engine')} {request.args.get('version')}", 500)
@server.get('/api/renderer/<renderer>/args')