feat: Add Cloudflare cache warming for terminal images

- Ensure the `terminal_images` directory exists and set permissions to 0o755.
- Implement a background task to warm the Cloudflare cache for newly generated terminal images by sending an HTTP GET request to the image URL.
- This helps ensure images are quickly available after being generated.
This commit is contained in:
Slipstream 2025-05-17 19:41:50 -06:00
parent c9d6cc40a6
commit 62bf7a36c9
Signed by: slipstream
GPG Key ID: 13E498CE010AC6FD

View File

@ -7,6 +7,8 @@ import os
import io
import uuid
import time
import aiohttp
import asyncio
from collections import deque
import shlex # For safer command parsing if not using shell=True for everything
@ -50,6 +52,17 @@ class TerminalCog(commands.Cog, name="Terminal"):
self.active_process: subprocess.Popen | None = None
self.terminal_view: TerminalView | None = None
self.last_command: str | None = None # Store the last command for display after execution
# Ensure the terminal_images directory exists with proper permissions
os.makedirs(TERMINAL_IMAGES_DIR, exist_ok=True)
# Try to set permissions to allow web server to read files
try:
# 0o755 = Owner can read/write/execute, others can read/execute
os.chmod(TERMINAL_IMAGES_DIR, 0o755)
print(f"Set permissions for {TERMINAL_IMAGES_DIR} to 0o755")
except Exception as e:
print(f"Warning: Could not set permissions for {TERMINAL_IMAGES_DIR}: {e}")
try:
self.font = ImageFont.truetype(FONT_PATH, FONT_SIZE)
except IOError:
@ -74,6 +87,23 @@ class TerminalCog(commands.Cog, name="Terminal"):
# Fallback or raise error
# self.owner_id = YOUR_FALLBACK_OWNER_ID # if you have one
async def _warm_cloudflare_cache(self, image_url: str):
"""
Sends a request to the image URL to warm up the Cloudflare cache.
Args:
image_url: The URL of the image to warm up
"""
try:
async with aiohttp.ClientSession() as session:
async with session.get(image_url, timeout=5) as response:
if response.status == 200:
print(f"Successfully warmed Cloudflare cache for {image_url}")
else:
print(f"Failed to warm Cloudflare cache for {image_url}: HTTP {response.status}")
except Exception as e:
print(f"Error warming Cloudflare cache for {image_url}: {e}")
def _generate_terminal_image(self) -> tuple[io.BytesIO, str]:
"""
Generates an image of the current terminal output.
@ -103,11 +133,9 @@ class TerminalCog(commands.Cog, name="Terminal"):
# Handle lines longer than image width (simple truncation)
# A more advanced version could wrap text or allow horizontal scrolling.
max_chars_on_line = (IMG_WIDTH - 2 * PADDING) // char_width if char_width > 0 else 80
# Truncate if needed
# Pillow's draw.text handles clipping, but explicit truncation can be clearer
# For simplicity, we'll let Pillow clip.
# For simplicity, we'll let Pillow clip text that's too long
# Uncomment the following code to implement manual truncation:
# max_chars_on_line = (IMG_WIDTH - 2 * PADDING) // char_width if char_width > 0 else 80
# if len(line) > max_chars_on_line:
# line = line[:max_chars_on_line-3] + "..."
@ -146,11 +174,15 @@ class TerminalCog(commands.Cog, name="Terminal"):
return
# Generate the image and save it to the terminal_images directory
image_bytes, filename = self._generate_terminal_image()
_, filename = self._generate_terminal_image()
# Create the URL for the image
image_url = f"{API_BASE_URL}/terminal_images/{filename}"
# Warm up the Cloudflare cache by sending a request to the image URL
# Use asyncio.create_task to run this in the background without waiting for it
asyncio.create_task(self._warm_cloudflare_cache(image_url))
if self.terminal_view:
self.terminal_view.update_button_states(self) # Update button enable/disable
@ -238,6 +270,10 @@ class TerminalCog(commands.Cog, name="Terminal"):
# Create the URL for the image
image_url = f"{API_BASE_URL}/terminal_images/{filename}"
# Warm up the Cloudflare cache by sending a request to the image URL
# Use asyncio.create_task to run this in the background without waiting for it
asyncio.create_task(self._warm_cloudflare_cache(image_url))
# Send initial message and store it
# Use followup since we deferred
self.terminal_message = await interaction.followup.send(