diff --git a/src/textual/driver.py b/src/textual/driver.py index 9a7fecb271..fe26f11db1 100644 --- a/src/textual/driver.py +++ b/src/textual/driver.py @@ -1,7 +1,7 @@ from __future__ import annotations import asyncio -import shutil +import threading from abc import ABC, abstractmethod from contextlib import contextmanager from pathlib import Path @@ -229,10 +229,25 @@ def deliver_binary( set the `Content-Type` header in the HTTP response. """ if isinstance(binary, BinaryIO): - with open(save_path, "wb") as destination_file: - shutil.copyfileobj(binary, destination_file) - else: # isinstance(binary, TextIO): - with open(save_path, "w", encoding=encoding) as destination_file: - shutil.copyfileobj(binary, destination_file) - - binary.close() + mode = "wb" + else: + mode = "w" + + def save_file_thread(): + with open(save_path, mode) as destination_file: + read = binary.read + write = destination_file.write + while True: + data = read(1024) + if not data: + break + write(data) + binary.close() + self._app.call_from_thread(self._delivery_complete, save_path=save_path) + + thread = threading.Thread(target=save_file_thread) + thread.start() + + def _delivery_complete(self, save_path: Path) -> None: + """Called when a file has been delivered.""" + self._app.post_message(events.DeliveryComplete(save_path=save_path)) diff --git a/src/textual/events.py b/src/textual/events.py index 13188d6845..fb7ad263d6 100644 --- a/src/textual/events.py +++ b/src/textual/events.py @@ -14,6 +14,7 @@ from __future__ import annotations from dataclasses import dataclass +from pathlib import Path from typing import TYPE_CHECKING, Type, TypeVar import rich.repr @@ -732,3 +733,11 @@ def __init__(self, text: str, stderr: bool = False) -> None: def __rich_repr__(self) -> rich.repr.Result: yield self.text yield self.stderr + + +@dataclass +class DeliveryComplete(Event, bubble=False): + """Sent to App when a file has been delivered.""" + + save_path: Path + """The save_path to the file that was delivered."""