diff --git a/domain/adapters/providers/telegram.py b/domain/adapters/providers/telegram.py index 8f4b445..799bd21 100644 --- a/domain/adapters/providers/telegram.py +++ b/domain/adapters/providers/telegram.py @@ -91,6 +91,7 @@ class TelegramAdapter: self._client: TelegramClient | None = None self._client_lock = asyncio.Lock() self._download_lock = asyncio.Lock() + self._active_stream_message_id: int | None = None self._message_cache: Dict[int, Tuple[float, object]] = {} @staticmethod @@ -517,35 +518,7 @@ class TelegramAdapter: raise NotImplementedError("Telegram 适配器不支持创建目录。") async def get_thumbnail(self, root: str, rel: str, size: str = "medium"): - try: - message_id_str, _ = rel.split('_', 1) - message_id = int(message_id_str) - except (ValueError, IndexError): - return None - - try: - client = await self._get_connected_client() - message = await self._get_cached_message(message_id) - if not message: - return None - - thumb = self._pick_photo_thumb(self._get_message_thumbs(message)) - if not thumb: - return None - - embedded = getattr(thumb, "bytes", None) - if embedded and isinstance(thumb, types.PhotoCachedSize): - return bytes(embedded) - if embedded and isinstance(thumb, types.PhotoStrippedSize): - return utils.stripped_photo_to_jpg(bytes(embedded)) - - async with self._download_lock: - result = await client.download_media(message, bytes, thumb=thumb) - if isinstance(result, (bytes, bytearray)): - return bytes(result) - return None - except Exception: - return None + return None async def delete(self, root: str, rel: str): """删除一个文件 (即一条消息)""" @@ -625,11 +598,14 @@ class TelegramAdapter: raise HTTPException(status_code=400, detail="Invalid Range header") headers["Content-Length"] = str(end - start + 1) + self._active_stream_message_id = message_id async def iterator(): downloaded = 0 try: limit = end - start + 1 + if self._active_stream_message_id != message_id: + return async with self._download_lock: async for chunk in client.iter_download( media, @@ -638,6 +614,8 @@ class TelegramAdapter: chunk_size=self._download_chunk_size, file_size=file_size, ): + if self._active_stream_message_id != message_id: + return if not chunk: continue remaining = limit - downloaded diff --git a/web/src/apps/PluginHost/index.tsx b/web/src/apps/PluginHost/index.tsx index 78ef901..e549864 100644 --- a/web/src/apps/PluginHost/index.tsx +++ b/web/src/apps/PluginHost/index.tsx @@ -24,6 +24,16 @@ function getPluginStylePaths(plugin: PluginItem): string[] { return styles.filter((s) => typeof s === 'string' && s.trim().length > 0); } +function unloadPluginFrame(iframe: HTMLIFrameElement | null) { + if (!iframe) return; + try { + iframe.contentWindow?.postMessage({ type: 'foxel-plugin:unload' }, window.location.origin); + } catch { + void 0; + } + iframe.src = 'about:blank'; +} + /** * 插件宿主组件 - 文件打开模式 * 使用 iframe 隔离渲染与样式,避免插件污染宿主 DOM/CSS。 @@ -66,7 +76,10 @@ export const PluginAppHost: React.FC = ({ }; window.addEventListener('message', onMessage); - return () => window.removeEventListener('message', onMessage); + return () => { + window.removeEventListener('message', onMessage); + unloadPluginFrame(iframeRef.current); + }; }, [plugin.key]); return ( @@ -118,7 +131,10 @@ export const PluginAppOpenHost: React.FC = ({ plugin, on }; window.addEventListener('message', onMessage); - return () => window.removeEventListener('message', onMessage); + return () => { + window.removeEventListener('message', onMessage); + unloadPluginFrame(iframeRef.current); + }; }, [plugin.key]); return ( diff --git a/web/src/plugin-frame.ts b/web/src/plugin-frame.ts index 81f3ce5..f43e187 100644 --- a/web/src/plugin-frame.ts +++ b/web/src/plugin-frame.ts @@ -364,12 +364,27 @@ async function main() { await mountError(); - window.addEventListener('beforeunload', () => { + const runCleanup = () => { try { cleanup?.(); } catch { void 0; } + cleanup = null; + }; + + window.addEventListener('message', (ev) => { + if (ev.origin !== window.location.origin) return; + if (ev.source !== window.parent) return; + const data = ev.data as any; + if (!data || typeof data !== 'object') return; + if (data.type !== 'foxel-plugin:unload') return; + runCleanup(); + root.innerHTML = ''; + }); + + window.addEventListener('beforeunload', () => { + runCleanup(); }); }