|
1 | | -import json |
2 | 1 | import shutil |
3 | | -import subprocess |
4 | | -import sys |
5 | 2 | import os |
6 | | -import stat |
7 | 3 | import re |
8 | 4 | import base64 |
| 5 | +import json |
| 6 | +import hashlib |
| 7 | +import urllib.parse |
9 | 8 | from pathlib import Path |
10 | 9 | from aiohttp import web |
11 | | -from typing import Dict, Any |
| 10 | +from typing import Any |
12 | 11 | from io import BytesIO |
13 | 12 | from PIL import Image |
14 | 13 |
|
|
18 | 17 | SAFE_FOLDER_NAME_REGEX, ALLOWED_EXTENSIONS, CUSTOM_THEMES_DIR, FLOWS_CONFIG_FILE |
19 | 18 | ) |
20 | 19 |
|
| 20 | +DATA_DIR = Path(__file__).parent / "data" |
| 21 | +PREVIEWS_REGISTRY_DIR = DATA_DIR / "model_previews_registry" |
| 22 | +PREVIEWS_IMAGES_DIR = DATA_DIR / "model_previews" |
| 23 | + |
| 24 | +def ensure_data_folders(): |
| 25 | + try: |
| 26 | + DATA_DIR.mkdir(parents=True, exist_ok=True) |
| 27 | + PREVIEWS_REGISTRY_DIR.mkdir(parents=True, exist_ok=True) |
| 28 | + PREVIEWS_IMAGES_DIR.mkdir(parents=True, exist_ok=True) |
| 29 | + except Exception as e: |
| 30 | + logger.error(f"{FLOWMSG}: Could not create data dirs: {e}") |
| 31 | + |
| 32 | +def pathToKey(model_path: str) -> str: |
| 33 | + return model_path.replace('\\', '/') |
| 34 | + |
| 35 | +def get_filename_only(model_path: str) -> str: |
| 36 | + fwd = pathToKey(model_path) |
| 37 | + return os.path.basename(fwd) |
| 38 | + |
| 39 | +def get_preview_id(model_path: str) -> str: |
| 40 | + filename = get_filename_only(model_path) |
| 41 | + h = hashlib.sha1(filename.encode('utf-8')).hexdigest() |
| 42 | + return h[:16] |
| 43 | + |
| 44 | +def get_preview_paths(preview_id: str): |
| 45 | + sub1 = preview_id[0] |
| 46 | + sub2 = preview_id[:2] |
| 47 | + registry_json = PREVIEWS_REGISTRY_DIR / sub1 / sub2 / f"{preview_id}.json" |
| 48 | + image_folder = PREVIEWS_IMAGES_DIR / sub1 / sub2 / preview_id |
| 49 | + return registry_json, image_folder |
| 50 | + |
| 51 | +async def set_model_preview_handler(request: web.Request) -> web.Response: |
| 52 | + try: |
| 53 | + ensure_data_folders() |
| 54 | + data = await request.json() |
| 55 | + rawPath = data.get("modelPath") |
| 56 | + base64_data = data.get("base64Data") |
| 57 | + if not rawPath or not base64_data: |
| 58 | + return web.Response(status=400, text="Missing 'modelPath' or 'base64Data'") |
| 59 | + |
| 60 | + pid = get_preview_id(rawPath) |
| 61 | + registry_json, image_folder = get_preview_paths(pid) |
| 62 | + |
| 63 | + match = re.match(r"data:(image/\w+);base64,(.+)", base64_data) |
| 64 | + if not match: |
| 65 | + return web.Response(status=400, text="Invalid data URL format") |
| 66 | + |
| 67 | + mime_type = match.group(1) |
| 68 | + encoded = match.group(2) |
| 69 | + try: |
| 70 | + raw_image = base64.b64decode(encoded) |
| 71 | + except: |
| 72 | + return web.Response(status=400, text="Error decoding base64 image") |
| 73 | + |
| 74 | + registry_json.parent.mkdir(parents=True, exist_ok=True) |
| 75 | + image_folder.mkdir(parents=True, exist_ok=True) |
| 76 | + |
| 77 | + full_path = image_folder / "full.jpg" |
| 78 | + with full_path.open("wb") as f: |
| 79 | + f.write(raw_image) |
| 80 | + |
| 81 | + thumb_path = image_folder / "thumbnail.jpg" |
| 82 | + img = Image.open(BytesIO(raw_image)) |
| 83 | + w_percent = 128.0 / float(img.size[0]) |
| 84 | + h_new = int(float(img.size[1]) * w_percent) |
| 85 | + img = img.convert("RGB").resize((128, h_new), Image.Resampling.LANCZOS) |
| 86 | + img.save(thumb_path, format="JPEG") |
| 87 | + |
| 88 | + reg_data = { |
| 89 | + "modelPath": rawPath, |
| 90 | + "previewId": pid, |
| 91 | + "timestamp": int(os.path.getmtime(full_path)), |
| 92 | + "mime_type": mime_type |
| 93 | + } |
| 94 | + with registry_json.open("w", encoding="utf-8") as jf: |
| 95 | + json.dump(reg_data, jf, indent=2) |
| 96 | + |
| 97 | + return web.json_response({"status": "success", "previewId": pid}) |
| 98 | + |
| 99 | + except Exception as e: |
| 100 | + logger.error(f"{FLOWMSG}: Error in set_model_preview_handler: {e}") |
| 101 | + return web.Response(status=500, text=str(e)) |
| 102 | + |
| 103 | +async def clear_model_preview_handler(request: web.Request) -> web.Response: |
| 104 | + try: |
| 105 | + ensure_data_folders() |
| 106 | + rawPath = request.query.get("modelPath", None) |
| 107 | + if not rawPath: |
| 108 | + return web.Response(status=400, text="Missing 'modelPath'") |
| 109 | + |
| 110 | + pid = get_preview_id(rawPath) |
| 111 | + registry_json, image_folder = get_preview_paths(pid) |
| 112 | + |
| 113 | + if registry_json.exists(): |
| 114 | + registry_json.unlink() |
| 115 | + if image_folder.exists() and image_folder.is_dir(): |
| 116 | + shutil.rmtree(image_folder) |
| 117 | + |
| 118 | + return web.json_response({"status": "success", "previewId": pid}) |
| 119 | + except Exception as e: |
| 120 | + logger.error(f"{FLOWMSG}: Error in clear_model_preview_handler: {e}") |
| 121 | + return web.Response(status=500, text=str(e)) |
| 122 | + |
| 123 | +async def list_model_previews_handler(request: web.Request) -> web.Response: |
| 124 | + try: |
| 125 | + ensure_data_folders() |
| 126 | + result_map = {} |
| 127 | + |
| 128 | + if request.method == 'POST': |
| 129 | + data = await request.json() |
| 130 | + raw_paths = data.get('paths', []) |
| 131 | + if not isinstance(raw_paths, list): |
| 132 | + return web.Response(status=400, text="Invalid JSON: 'paths' must be an array") |
| 133 | + |
| 134 | + for rp in raw_paths: |
| 135 | + rp = urllib.parse.unquote(rp).strip() |
| 136 | + if not rp: |
| 137 | + continue |
| 138 | + pid = get_preview_id(rp) |
| 139 | + _, image_folder = get_preview_paths(pid) |
| 140 | + thumb = image_folder / "thumbnail.jpg" |
| 141 | + if thumb.exists(): |
| 142 | + with thumb.open("rb") as tf: |
| 143 | + b = tf.read() |
| 144 | + b64 = base64.b64encode(b).decode("utf-8") |
| 145 | + data_url = f"data:image/jpeg;base64,{b64}" |
| 146 | + result_map[rp] = data_url |
| 147 | + |
| 148 | + return web.json_response(result_map) |
| 149 | + |
| 150 | + paths_param = request.rel_url.query.get('paths', None) |
| 151 | + if paths_param: |
| 152 | + raw_split = paths_param.split(',') |
| 153 | + for rp in raw_split: |
| 154 | + rp = rp.strip() |
| 155 | + if not rp: |
| 156 | + continue |
| 157 | + rp = urllib.parse.unquote(rp) |
| 158 | + pid = get_preview_id(rp) |
| 159 | + _, image_folder = get_preview_paths(pid) |
| 160 | + thumb = image_folder / "thumbnail.jpg" |
| 161 | + if thumb.exists(): |
| 162 | + with thumb.open("rb") as tf: |
| 163 | + b = tf.read() |
| 164 | + b64 = base64.b64encode(b).decode("utf-8") |
| 165 | + result_map[rp] = f"data:image/jpeg;base64,{b64}" |
| 166 | + return web.json_response(result_map) |
| 167 | + |
| 168 | + for root, dirs, files in os.walk(PREVIEWS_REGISTRY_DIR): |
| 169 | + for filename in files: |
| 170 | + if filename.endswith(".json"): |
| 171 | + regp = Path(root) / filename |
| 172 | + try: |
| 173 | + with regp.open("r", encoding="utf-8") as f: |
| 174 | + reg_data = json.load(f) |
| 175 | + mp = reg_data.get("modelPath") |
| 176 | + pid = reg_data.get("previewId") |
| 177 | + if not mp or not pid: |
| 178 | + continue |
| 179 | + |
| 180 | + _, folder = get_preview_paths(pid) |
| 181 | + thumb = folder / "thumbnail.jpg" |
| 182 | + if thumb.exists(): |
| 183 | + with thumb.open("rb") as tf: |
| 184 | + b = tf.read() |
| 185 | + b64 = base64.b64encode(b).decode("utf-8") |
| 186 | + result_map[mp] = f"data:image/jpeg;base64,{b64}" |
| 187 | + except Exception as ex: |
| 188 | + logger.error(f"{FLOWMSG}: Error reading registry {regp}: {ex}") |
| 189 | + continue |
| 190 | + |
| 191 | + return web.json_response(result_map) |
| 192 | + |
| 193 | + except Exception as e: |
| 194 | + logger.error(f"{FLOWMSG}: Error in list_model_previews_handler: {e}") |
| 195 | + return web.Response(status=500, text=str(e)) |
| 196 | + |
| 197 | +async def get_model_preview_handler(request: web.Request) -> web.Response: |
| 198 | + try: |
| 199 | + ensure_data_folders() |
| 200 | + rawPath = request.query.get("modelPath", None) |
| 201 | + if not rawPath: |
| 202 | + return web.Response(status=400, text="Missing 'modelPath'") |
| 203 | + |
| 204 | + pid = get_preview_id(rawPath) |
| 205 | + _, image_folder = get_preview_paths(pid) |
| 206 | + |
| 207 | + thumb = image_folder / "thumbnail.jpg" |
| 208 | + if thumb.exists(): |
| 209 | + with thumb.open("rb") as tf: |
| 210 | + b = tf.read() |
| 211 | + b64 = base64.b64encode(b).decode("utf-8") |
| 212 | + data_url = f"data:image/jpeg;base64,{b64}" |
| 213 | + return web.json_response({rawPath: data_url}) |
| 214 | + else: |
| 215 | + return web.Response(status=404, text="Preview not found") |
| 216 | + |
| 217 | + except Exception as e: |
| 218 | + logger.error(f"{FLOWMSG}: Error in get_model_preview_handler: {e}") |
| 219 | + return web.Response(status=500, text=str(e)) |
| 220 | + |
21 | 221 | async def apps_handler(request: web.Request) -> web.Response: |
22 | 222 | return web.json_response(APP_CONFIGS) |
23 | 223 |
|
|
0 commit comments