Skip to main content

wslplugins_rs/
offline_distribution_information.rs

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