use crate::com::{ProcessMtaGuard, init_process_mta};
use crate::container::{ContainerBuilder, WslcContainerHandle};
use crate::error::WslcError;
use crate::image::{ImageInfo, WslcImageManager};
use crate::process::ProcessBuilder;
use crate::registry::{AuthTokenResult, WslcRegistryManager};
use crate::session::{SessionBuilder, WslcSessionHandle};
use crate::system::WslcSystem;
use crate::volume::{VhdVolumeOptions, WslcVolumeManager};
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;
use wslcsdk_sys::types::WslcVersion;
#[derive(Debug)]
struct ClientInner {
_mta_guard: Option<ProcessMtaGuard>,
session: WslcSessionHandle,
}
#[derive(Clone, Debug)]
pub struct WslcClient {
inner: Arc<ClientInner>,
}
impl WslcClient {
pub fn new() -> Result<Self, WslcError> {
WslcClientBuilder::new().build()
}
pub fn builder() -> WslcClientBuilder {
WslcClientBuilder::new()
}
pub fn session(&self) -> WslcSessionHandle {
self.inner.session.clone()
}
#[must_use]
pub fn is_mta_active(&self) -> bool {
self.inner._mta_guard.is_some()
}
pub fn version(&self) -> Result<WslcVersion, WslcError> {
WslcSystem::get_version()
}
pub fn create_container(&self, image: impl Into<String>) -> BoundContainerBuilder<'_> {
BoundContainerBuilder {
inner: ContainerBuilder::new(image),
session: &self.inner.session,
}
}
pub fn open_container(&self, name_or_id: &str) -> Result<WslcContainerHandle, WslcError> {
WslcContainerHandle::open(&self.inner.session, name_or_id)
}
pub fn list_images(&self) -> Result<Vec<ImageInfo>, WslcError> {
WslcImageManager::list_images(&self.inner.session)
}
pub fn delete_image(&self, name_or_id: &str) -> Result<(), WslcError> {
WslcImageManager::delete_image(&self.inner.session, name_or_id)
}
pub fn create_vhd_volume(&self, options: &VhdVolumeOptions<'_>) -> Result<(), WslcError> {
WslcVolumeManager::create_vhd_volume(&self.inner.session, options)
}
pub fn delete_vhd_volume(&self, name: &str) -> Result<(), WslcError> {
WslcVolumeManager::delete_vhd_volume(&self.inner.session, name)
}
pub fn authenticate(
&self,
server_address: &str,
username: &str,
password: &str,
) -> Result<AuthTokenResult, WslcError> {
WslcRegistryManager::authenticate(&self.inner.session, server_address, username, password)
}
}
#[derive(Debug)]
pub struct BoundContainerBuilder<'a> {
inner: ContainerBuilder,
session: &'a WslcSessionHandle,
}
impl<'a> BoundContainerBuilder<'a> {
pub fn build(self) -> Result<WslcContainerHandle, WslcError> {
self.inner.build(self.session)
}
pub fn name(mut self, name: impl Into<String>) -> Self {
self.inner = self.inner.name(name);
self
}
pub fn auto_remove(mut self, auto: bool) -> Self {
self.inner = self.inner.auto_remove(auto);
self
}
pub fn networking_mode(mut self, mode: crate::WslcContainerNetworkingMode) -> Self {
self.inner = self.inner.networking_mode(mode);
self
}
pub fn host_name(mut self, host: impl Into<String>) -> Self {
self.inner = self.inner.host_name(host);
self
}
pub fn domain_name(mut self, domain: impl Into<String>) -> Self {
self.inner = self.inner.domain_name(domain);
self
}
pub fn flags(mut self, flags: crate::WslcContainerFlags) -> Self {
self.inner = self.inner.flags(flags);
self
}
pub fn add_port_mapping(
mut self,
windows_port: u16,
container_port: u16,
protocol: crate::WslcPortProtocol,
) -> Self {
self.inner = self
.inner
.add_port_mapping(windows_port, container_port, protocol);
self
}
pub fn add_volume(
mut self,
windows_path: impl AsRef<Path>,
container_path: &str,
read_only: bool,
) -> Self {
self.inner = self
.inner
.add_volume(windows_path, container_path, read_only);
self
}
pub fn add_named_volume(mut self, name: &str, container_path: &str, read_only: bool) -> Self {
self.inner = self.inner.add_named_volume(name, container_path, read_only);
self
}
pub fn init_process(mut self, process: ProcessBuilder) -> Self {
self.inner = self.inner.init_process(process);
self
}
pub fn into_inner(self) -> ContainerBuilder {
self.inner
}
}
#[derive(Debug, Default)]
pub struct WslcClientBuilder {
auto_init_mta: bool,
session_name: Option<String>,
session_dir: Option<PathBuf>,
}
impl WslcClientBuilder {
pub fn new() -> Self {
Self {
auto_init_mta: true,
session_name: None,
session_dir: None,
}
}
pub fn auto_init_mta(mut self, enabled: bool) -> Self {
self.auto_init_mta = enabled;
self
}
pub fn session_name(mut self, name: impl Into<String>) -> Self {
self.session_name = Some(name.into());
self
}
pub fn session_dir(mut self, dir: impl Into<PathBuf>) -> Self {
self.session_dir = Some(dir.into());
self
}
pub fn build(self) -> Result<WslcClient, WslcError> {
WslcSystem::ensure_sdk_available()?;
let session = match self.session_dir {
Some(dir) => {
SessionBuilder::new(self.session_name.unwrap_or_else(default_name), dir).build()
}
None => SessionBuilder::new_default(self.session_name.unwrap_or_else(default_name))
.and_then(|builder| builder.build()),
}?;
let mta_guard = if self.auto_init_mta {
init_process_mta()
.inspect_err(|e| {
log::warn!(
"进程级 COM MTA 守护建立失败,客户端继续运行,各句柄将依赖自身的进程级租约: {e}"
);
})
.ok()
} else {
None
};
log::info!(
"WSLC 客户端创建就绪,会话名称: '{}',进程级 MTA 守护: {}",
session.name(),
if mta_guard.is_some() {
"已启用"
} else {
"未启用"
}
);
Ok(WslcClient {
inner: Arc::new(ClientInner {
_mta_guard: mta_guard,
session,
}),
})
}
}
fn default_name() -> String {
"default".to_string()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::WslcDomainError;
#[test]
fn test_sdk_probe_reports_availability_without_panicking() {
match WslcSystem::ensure_sdk_available() {
Ok(()) => assert!(WslcSystem::is_sdk_available()),
Err(WslcError::Domain(WslcDomainError::SdkUpdateNeeded(_))) => {
assert!(!WslcSystem::is_sdk_available())
}
Err(other) => panic!("预期返回 SdkUpdateNeeded,实际为: {other:?}"),
}
}
#[test]
fn test_default_name_is_used_when_not_specified() {
assert_eq!(default_name(), "default");
}
#[test]
fn test_builder_defaults() {
let builder = WslcClientBuilder::new();
assert!(builder.auto_init_mta, "默认应自动维持进程级 MTA 套间");
assert!(builder.session_name.is_none(), "默认不应预设会话名");
assert!(builder.session_dir.is_none(), "默认不应预设会话目录");
}
#[test]
fn test_builder_accepts_session_overrides() {
let builder = WslcClientBuilder::new()
.auto_init_mta(false)
.session_name("custom")
.session_dir("C:\\temp");
assert!(!builder.auto_init_mta);
assert_eq!(builder.session_name.as_deref(), Some("custom"));
assert_eq!(builder.session_dir.as_deref(), Some(Path::new("C:\\temp")));
}
}