concord 2.2.3

A terminal user interface client for Discord
Documentation
use std::sync::Arc;

use tokio::sync::Semaphore;
use tokio::time::timeout;

use crate::{
    DiscordClient,
    discord::{AppCommand, AppEvent, MediaPlaybackRequestId, read_profile_avatar_image},
    logging,
};

use super::media_adapters;

pub(super) async fn handle(
    client: DiscordClient,
    command: AppCommand,
    attachment_preview_permits: Arc<Semaphore>,
    attachment_download_permits: Arc<Semaphore>,
) {
    match command {
        AppCommand::LoadAttachmentPreview { url } => {
            let Ok(_permit) = attachment_preview_permits.acquire_owned().await else {
                let message = "attachment preview limiter closed".to_owned();
                logging::error("preview", &message);
                client
                    .publish_event(AppEvent::AttachmentPreviewLoadFailed { url, message })
                    .await;
                return;
            };
            match timeout(
                media_adapters::ATTACHMENT_PREVIEW_TIMEOUT,
                media_adapters::fetch_attachment_preview(&url),
            )
            .await
            {
                Err(_) => {
                    let message = "download image preview timed out".to_owned();
                    logging::error("preview", &message);
                    client
                        .publish_event(AppEvent::AttachmentPreviewLoadFailed { url, message })
                        .await;
                }
                Ok(bytes) => match bytes {
                    Ok(bytes) => {
                        client
                            .publish_event(AppEvent::AttachmentPreviewLoaded { url, bytes })
                            .await
                    }
                    Err(message) => {
                        logging::error("preview", &message);
                        client
                            .publish_event(AppEvent::AttachmentPreviewLoadFailed { url, message })
                            .await;
                    }
                },
            }
        }
        AppCommand::LoadProfileAvatarPreview { key, upload } => {
            match read_profile_avatar_image(&upload).await {
                Ok(image) => {
                    client
                        .publish_event(AppEvent::AttachmentPreviewLoaded {
                            url: key,
                            bytes: image.bytes,
                        })
                        .await;
                }
                Err(message) => {
                    logging::error("preview", &message);
                    client
                        .publish_event(AppEvent::AttachmentPreviewLoadFailed { url: key, message })
                        .await;
                }
            }
        }
        AppCommand::OpenUrl { url } => {
            if let Err(error) = media_adapters::open_url(&url) {
                logging::error("app", format!("open url failed: {error}"));
                client
                    .publish_event(AppEvent::GatewayError {
                        message: format!("open url failed: {error}"),
                    })
                    .await;
            }
        }
        AppCommand::PlayMedia { target, request_id } => {
            let request_id = request_id.unwrap_or_else(|| MediaPlaybackRequestId::new(0));
            if let Err(error) =
                media_adapters::play_media(client.clone(), request_id, &target.url, &target.label)
                    .await
            {
                logging::error("media", format!("play media failed: {error}"));
                let label = if target.label.is_empty() {
                    "media"
                } else {
                    target.label.as_str()
                };
                client
                    .publish_event(AppEvent::GatewayError {
                        message: format!("play {label} failed: {error}"),
                    })
                    .await;
            }
        }
        AppCommand::DownloadAttachment {
            id,
            url,
            filename,
            source,
        } => {
            let Ok(_permit) = attachment_download_permits.acquire_owned().await else {
                let message = "attachment download limiter closed".to_owned();
                logging::error("attachment", &message);
                client
                    .publish_event(AppEvent::AttachmentDownloadFailed {
                        id,
                        filename,
                        message,
                        source,
                    })
                    .await;
                return;
            };
            match media_adapters::download_attachment(&client, id, &url, &filename, source).await {
                Ok(path) => {
                    client
                        .publish_event(AppEvent::AttachmentDownloadCompleted {
                            id,
                            path: path.display().to_string(),
                            source,
                        })
                        .await
                }
                Err(message) => {
                    logging::error("attachment", &message);
                    client
                        .publish_event(AppEvent::AttachmentDownloadFailed {
                            id,
                            filename,
                            message,
                            source,
                        })
                        .await;
                }
            }
        }
        _ => unreachable!("non-media command routed to media handler"),
    }
}