|
1 | 1 | import datetime
|
| 2 | +import logging |
| 3 | +import mimetypes |
| 4 | +import urllib.parse |
2 | 5 | from typing import Final
|
3 | 6 |
|
4 | 7 | from aiohttp import web
|
5 | 8 | from models_library.projects import ProjectID
|
| 9 | +from models_library.projects_nodes import Node, NodeID |
| 10 | +from models_library.projects_nodes_io import SimCoreFileLink |
6 | 11 | from models_library.users import UserID
|
7 |
| -from pydantic import NonNegativeFloat, NonNegativeInt |
| 12 | +from pydantic import ( |
| 13 | + BaseModel, |
| 14 | + Field, |
| 15 | + HttpUrl, |
| 16 | + NonNegativeFloat, |
| 17 | + NonNegativeInt, |
| 18 | + ValidationError, |
| 19 | + parse_obj_as, |
| 20 | + root_validator, |
| 21 | +) |
8 | 22 |
|
| 23 | +from .._constants import APP_SETTINGS_KEY, RQT_USERID_KEY |
9 | 24 | from ..application_settings import get_settings
|
| 25 | +from ..storage.api import get_download_link |
10 | 26 | from .exceptions import ProjectStartsTooManyDynamicNodes
|
11 | 27 |
|
| 28 | +_logger = logging.getLogger(__name__) |
12 | 29 | _NODE_START_INTERVAL_S: Final[datetime.timedelta] = datetime.timedelta(seconds=15)
|
13 | 30 |
|
14 | 31 |
|
@@ -43,3 +60,107 @@ def get_total_project_dynamic_nodes_creation_interval(
|
43 | 60 | director-v2. Note: these calls are sent one after the other.
|
44 | 61 | """
|
45 | 62 | return max_nodes * _NODE_START_INTERVAL_S.total_seconds()
|
| 63 | + |
| 64 | + |
| 65 | +# |
| 66 | +# PREVIEWS |
| 67 | +# |
| 68 | + |
| 69 | + |
| 70 | +class NodeScreenshot(BaseModel): |
| 71 | + thumbnail_url: HttpUrl |
| 72 | + file_url: HttpUrl |
| 73 | + mimetype: str | None = Field( |
| 74 | + default=None, |
| 75 | + description="File's media type or None if unknown. SEE https://www.iana.org/assignments/media-types/media-types.xhtml", |
| 76 | + example="image/jpeg", |
| 77 | + ) |
| 78 | + |
| 79 | + @root_validator(pre=True) |
| 80 | + @classmethod |
| 81 | + def guess_mimetype_if_undefined(cls, values): |
| 82 | + mimetype = values.get("mimetype") |
| 83 | + |
| 84 | + if mimetype is None: |
| 85 | + file_url = values["file_url"] |
| 86 | + assert file_url # nosec |
| 87 | + |
| 88 | + _type, _encoding = mimetypes.guess_type(file_url) |
| 89 | + # NOTE: mimetypes.guess_type works differently in our image, therefore made it nullable if |
| 90 | + # cannot guess type |
| 91 | + # SEE https://github.com/ITISFoundation/osparc-simcore/issues/4385 |
| 92 | + values["mimetype"] = _type |
| 93 | + |
| 94 | + return values |
| 95 | + |
| 96 | + |
| 97 | +async def fake_screenshots_factory( |
| 98 | + request: web.Request, node_id: NodeID, node: Node |
| 99 | +) -> list[NodeScreenshot]: |
| 100 | + """ |
| 101 | + ONLY for testing purposes |
| 102 | +
|
| 103 | + """ |
| 104 | + assert request.app[APP_SETTINGS_KEY].WEBSERVER_DEV_FEATURES_ENABLED # nosec |
| 105 | + screenshots = [] |
| 106 | + |
| 107 | + if ( |
| 108 | + "file-picker" in node.key |
| 109 | + and "fake" in node.label.lower() |
| 110 | + and node.outputs is not None |
| 111 | + ): |
| 112 | + # Example of file that can be added in file-picker: |
| 113 | + # Example https://github.com/Ybalrid/Ogre_glTF/raw/6a59adf2f04253a3afb9459549803ab297932e8d/Media/Monster.glb |
| 114 | + try: |
| 115 | + user_id = request[RQT_USERID_KEY] |
| 116 | + text = urllib.parse.quote(node.label) |
| 117 | + |
| 118 | + assert node.outputs is not None # nosec |
| 119 | + |
| 120 | + filelink = parse_obj_as(SimCoreFileLink, node.outputs["outFile"]) |
| 121 | + |
| 122 | + file_url = await get_download_link(request.app, user_id, filelink) |
| 123 | + screenshots.append( |
| 124 | + NodeScreenshot( |
| 125 | + thumbnail_url=f"https://placehold.co/170x120?text={text}", |
| 126 | + file_url=file_url, |
| 127 | + ) |
| 128 | + ) |
| 129 | + except (KeyError, ValidationError) as err: |
| 130 | + _logger.debug( |
| 131 | + "Failed to create link from file-picker %s: %s", |
| 132 | + node.json(indent=1), |
| 133 | + err, |
| 134 | + ) |
| 135 | + |
| 136 | + elif node.key.startswith("simcore/services/dynamic"): |
| 137 | + # For dynamic services, just create fake images |
| 138 | + |
| 139 | + # References: |
| 140 | + # - https://github.com/Ybalrid/Ogre_glTF |
| 141 | + # - https://placehold.co/ |
| 142 | + # - https://picsum.photos/ |
| 143 | + # |
| 144 | + count = int(str(node_id.int)[0]) |
| 145 | + text = urllib.parse.quote(node.label) |
| 146 | + |
| 147 | + screenshots = [ |
| 148 | + *( |
| 149 | + NodeScreenshot( |
| 150 | + thumbnail_url=f"https://picsum.photos/seed/{node_id.int + n}/170/120", |
| 151 | + file_url=f"https://picsum.photos/seed/{node_id.int + n}/500", |
| 152 | + mimetype="image/jpeg", |
| 153 | + ) |
| 154 | + for n in range(count) |
| 155 | + ), |
| 156 | + *( |
| 157 | + NodeScreenshot( |
| 158 | + thumbnail_url=f"https://placehold.co/170x120?text={text}", |
| 159 | + file_url=f"https://placehold.co/500x500?text={text}", |
| 160 | + mimetype="image/svg+xml", |
| 161 | + ) |
| 162 | + for n in range(count) |
| 163 | + ), |
| 164 | + ] |
| 165 | + |
| 166 | + return screenshots |
0 commit comments