Repository: AmbientRun/Ambient
File: crates/deploy/src/lib.rs (Rust)

Code before:
pub mod deploy_proto {
    include!("../proto/ambient.run.deploy.rs");
}

use std::{
    collections::HashMap,
    path::{Path, PathBuf},
    str::FromStr,
};

use ambient_project::Manifest;
use md5::Digest;
use tokio_stream::StreamExt;
use tonic::{
    codegen::{CompressionEncoding, InterceptedService},
    metadata::MetadataValue,
    service::Interceptor,
    transport::{Certificate, Channel, ClientTlsConfig, Uri},
    Request,
};
use walkdir::WalkDir;

use deploy_proto::{
    asset_content::ContentDescription, deploy_asset_response::Message,
    deployer_client::DeployerClient, AssetContent, DeployAssetRequest, VersionDeployed,
};

const CHUNK_SIZE: usize = 1024 * 1024 * 3; // 3MB

async fn asset_requests_from_file_path(
    ember_id: impl AsRef<str>,
    asset_path: impl AsRef<str>,
    file_path: impl AsRef<Path>,
) -> anyhow::Result<Vec<DeployAssetRequest>> {
    let content = ambient_sys::fs::read(file_path.as_ref()).await?;
    let total_size = content.len() as u64;

    // using single AssetContent per chunk because gRPC message has to be <4MB
    Ok(content
        .chunks(CHUNK_SIZE)
        .map(|chunk| DeployAssetRequest {
            ember_id: ember_id.as_ref().into(),
            contents: vec![AssetContent {
                path: asset_path.as_ref().into(),
                total_size,
                content_description: Some(ContentDescription::Chunk(chunk.to_vec())),
            }],
        })
        .collect())
}

Review comment (bug):
I think failing here if a file path isn't UTF-8 would be preferred, since this can map different files to the same value, which can be a very nasty bug to figure out, especially for users who don't know this happens, as they think they did some configuring wrong.

Code after:
pub mod deploy_proto {
    include!("../proto/ambient.run.deploy.rs");
}

use std::{
    collections::HashMap,
    path::{Path, PathBuf},
    str::FromStr,
};

use ambient_project::Manifest;
use md5::Digest;
use tokio_stream::StreamExt;
use tonic::{
    codegen::{CompressionEncoding, InterceptedService},
    metadata::MetadataValue,
    service::Interceptor,
    transport::{Certificate, Channel, ClientTlsConfig, Uri},
    Request,
};
use walkdir::WalkDir;

use deploy_proto::{
    asset_content::ContentDescription, deploy_asset_response::Message,
    deployer_client::DeployerClient, AssetContent, DeployAssetRequest, VersionDeployed,
};

const CHUNK_SIZE: usize = 1024 * 1024 * 3; // 3MB

async fn asset_requests_from_file_path(
    ember_id: impl AsRef<str>,
    asset_path: impl AsRef<str>,
    file_path: impl AsRef<Path>,
) -> anyhow::Result<Vec<DeployAssetRequest>> {
    let content = ambient_sys::fs::read(file_path.as_ref()).await?;
    let total_size = content.len() as u64;

    // handle empty file
    if content.is_empty() {
        return Ok(vec![DeployAssetRequest {
            ember_id: ember_id.as_ref().into(),
            contents: vec![AssetContent {
                path: asset_path.as_ref().into(),
                total_size,
                content_description: Some(ContentDescription::Chunk(content)),
            }],
        }]);
    }

    // using single AssetContent per chunk because gRPC message has to be <4MB