Skip to main content

wslplugins_rs/
wsl_offline_distribution_information.rs

1//! # WSL Offline Distribution Information
2//!
3//! This module provides an abstraction over `WslOfflineDistributionInformation` from the WSL Plugin API,
4//! offering a safe and idiomatic Rust interface for accessing offline distribution details.
5
6use crate::{
7    api::{errors::require_update_error::Result, utils::check_capability_result_from_context},
8    core_wsl_distribution_information::CoreWSLDistributionInformation,
9    utils::opt_wide_str,
10    UserDistributionID, WSLContext, WSLVersionCapability,
11};
12use std::{
13    ffi::OsString,
14    fmt::{self, Debug, Display},
15    hash::{Hash, Hasher},
16    os::windows::ffi::OsStringExt as _,
17    ptr,
18};
19use windows_core::PCWSTR;
20
21/// A wrapper around `WslOfflineDistributionInformation` providing a safe interface.
22///
23/// This struct allows access to the details of an offline WSL distribution, including
24/// its ID, name, and optional package family name.
25#[repr(transparent)]
26pub struct WSLOfflineDistributionInformation(wslpluginapi_sys::WslOfflineDistributionInformation);
27
28impl From<WSLOfflineDistributionInformation>
29    for wslpluginapi_sys::WslOfflineDistributionInformation
30{
31    #[inline]
32    fn from(value: WSLOfflineDistributionInformation) -> Self {
33        value.0
34    }
35}
36
37impl From<wslpluginapi_sys::WslOfflineDistributionInformation>
38    for WSLOfflineDistributionInformation
39{
40    #[inline]
41    fn from(value: wslpluginapi_sys::WslOfflineDistributionInformation) -> Self {
42        Self(value)
43    }
44}
45
46impl AsRef<wslpluginapi_sys::WslOfflineDistributionInformation>
47    for WSLOfflineDistributionInformation
48{
49    #[inline]
50    fn as_ref(&self) -> &wslpluginapi_sys::WslOfflineDistributionInformation {
51        &self.0
52    }
53}
54
55impl AsRef<WSLOfflineDistributionInformation>
56    for wslpluginapi_sys::WslOfflineDistributionInformation
57{
58    #[inline]
59    fn as_ref(&self) -> &WSLOfflineDistributionInformation {
60        // SAFETY: This conversion is safe because of transparency.
61        unsafe { &*ptr::from_ref::<Self>(self).cast::<WSLOfflineDistributionInformation>() }
62    }
63}
64
65impl CoreWSLDistributionInformation for WSLOfflineDistributionInformation {
66    #[inline]
67    fn id(&self) -> UserDistributionID {
68        self.0.Id.into()
69    }
70
71    /// Retrieves the name of the offline distribution as an [`OsString`].
72    #[inline]
73    fn name(&self) -> OsString {
74        // SAFETY: name is known to be valid
75        unsafe { OsString::from_wide(PCWSTR::from_raw(self.0.Name).as_wide()) }
76    }
77
78    /// Retrieves the package family name of the offline distribution, if available.
79    ///
80    /// # Returns
81    /// - `Some(OsString)`: If the package family name is set.
82    /// - `None`: If the package family name is null or empty.
83    #[inline]
84    fn package_family_name(&self) -> Option<OsString> {
85        opt_wide_str(self.0.PackageFamilyName)
86    }
87
88    /// Retrieves the distribution flavor.
89    ///
90    /// This requires [`WSLVersionCapability::DistributionFlavor`] (`2.4.4`).
91    #[inline]
92    fn flavor(&self) -> Result<Option<OsString>> {
93        check_capability_result_from_context(
94            WSLContext::get_current(),
95            WSLVersionCapability::DistributionFlavor,
96        )?;
97        Ok(opt_wide_str(self.0.Flavor))
98    }
99
100    /// Retrieves the distribution version.
101    ///
102    /// This requires [`WSLVersionCapability::DistributionVersion`] (`2.4.4`).
103    #[inline]
104    fn version(&self) -> Result<Option<OsString>> {
105        check_capability_result_from_context(
106            WSLContext::get_current(),
107            WSLVersionCapability::DistributionVersion,
108        )?;
109        Ok(opt_wide_str(self.0.Version))
110    }
111}
112
113impl<T> PartialEq<T> for WSLOfflineDistributionInformation
114where
115    T: CoreWSLDistributionInformation,
116{
117    /// Compares two distributions by their IDs for equality.
118    #[inline]
119    fn eq(&self, other: &T) -> bool {
120        self.id() == other.id()
121    }
122}
123
124impl Hash for WSLOfflineDistributionInformation {
125    /// Computes a hash based on the distribution's ID.
126    #[inline]
127    fn hash<H: Hasher>(&self, state: &mut H) {
128        self.id().hash(state);
129    }
130}
131
132impl Display for WSLOfflineDistributionInformation {
133    #[inline]
134    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135        // SAFETY: Name is known to be valid
136        unsafe {
137            write!(
138                f,
139                "{} {{{}}}",
140                PCWSTR::from_raw(self.0.Name).display(),
141                self.id()
142            )
143        }
144    }
145}
146
147impl Debug for WSLOfflineDistributionInformation {
148    #[inline]
149    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
150        let mut dbg = f.debug_struct(stringify!(WSLOfflineDistributionInformation));
151        dbg.field("name", &self.name())
152            .field("id", &self.id())
153            .field("package_family_name", &self.package_family_name());
154        let mut exhaustive = true;
155        if let Ok(flavor) = self.flavor() {
156            dbg.field("flavor", &flavor);
157        } else {
158            exhaustive = false;
159        }
160        if let Ok(version) = self.version() {
161            dbg.field("version", &version);
162        } else {
163            exhaustive = false;
164        }
165        if exhaustive {
166            dbg.finish()
167        } else {
168            dbg.finish_non_exhaustive()
169        }
170    }
171}
172
173#[cfg(test)]
174mod tests {
175    use super::*;
176    use crate::utils::test_transparence;
177
178    #[test]
179    fn test_layouts() {
180        test_transparence::<
181            wslpluginapi_sys::WslOfflineDistributionInformation,
182            WSLOfflineDistributionInformation,
183        >();
184    }
185}