Skip to content

Commit

Permalink
feat: make DialogFileBrowserOptions::new() use native initialization …
Browse files Browse the repository at this point in the history
…function
  • Loading branch information
JarvisCraft committed Sep 11, 2024
1 parent da24fc6 commit 14ef1b9
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 23 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
`fn(Option<&CStr>) -> i32` instead of `fn(*mut u8) -> i32`.
- `flipperzero::furi::string::FuriString::as_mut_ptr` is now public to allow for
it to be used with low-level C APIs (e.g. `furi_string_printf`).
- `flipperzero::dialogs::DialogFileBrowserOptions` now uses native initialization function.

### Removed

Expand All @@ -37,6 +38,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- `flipperzero::furi::time::{Duration, Instant}` implementations.
- `impl Default for flipperzero::dialogs::DialogMessage`
- `impl Default for flipperzero::toolbox::Crc32`
- `flipperzero::dialogs::DialogFileBrowserOptions`

### Changed

Expand Down
116 changes: 93 additions & 23 deletions crates/flipperzero/src/dialogs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
#[cfg(feature = "alloc")]
use alloc::ffi::CString;

use core::ffi::{c_void, CStr};
use core::ffi::{c_char, c_void, CStr};
use core::marker::PhantomData;
use core::mem::MaybeUninit;
use core::ptr::{self, NonNull};

use flipperzero_sys as sys;
Expand All @@ -25,6 +26,7 @@ pub struct DialogMessage<'a> {
}

/// A dialog file browser options.
#[repr(transparent)]
pub struct DialogFileBrowserOptions<'a> {
data: sys::DialogsFileBrowserOptions,
_phantom: PhantomData<&'a ()>,
Expand Down Expand Up @@ -215,40 +217,108 @@ impl<'a> Default for DialogFileBrowserOptions<'a> {
}

impl<'a> DialogFileBrowserOptions<'a> {
/// Creates a new dialog file browser options and initializes to default values.
pub fn new() -> Self {
// SAFETY: the string is a valid UTF-8
unsafe { Self::with_extension(c"*") }
}
/// Creates a new dialog file browser options and initializes to default values.
///
/// # Safety
///
/// `extension` should be a valid UTF-8 string
///
/// # Compatibility
///
/// This function's signature may change in the future to make it safe.
///
/// # Examples
///
/// Basic usage:
///
/// ```
/// # use flipperzero::dialogs::DialogFileBrowserOptions;
/// let options = DialogFileBrowserOptions::new(c"*");
/// ```
///
/// ## Lifetime covariance:
///
/// Even if `'static` lifetime is involved in the creation of options,
/// the resulting lifetime will be the most applicable one:
///
/// ```
/// # use core::ffi::CStr;
/// # use flipperzero::dialogs::DialogFileBrowserOptions;
/// // has `'static` lifetime
/// const EXTENSION: &CStr = c"txt";
/// // has "local" lifetime, aka `'a`
/// let base_path_bytes = [b'/', b'r', b'o', b'o', b't'];
/// let base_path = CStr::from_bytes_with_nul(&base_path_bytes).unwrap();
/// // the most appropriate lifetime `'a` is used
/// // SAFETY: `EXTENSION` is a valid UTF-8 string
/// let mut options = unsafe { DialogFileBrowserOptions::new(EXTENSION) }
/// .set_base_path(base_path);
/// ```
///
/// Still this should not allow the options to outlive its components:
///
/// ```compile_fail
/// # use core::ffi::CStr;
/// # use flipperzero::dialogs::DialogFileBrowserOptions;
/// # use flipperzero_sys::{cstr, DialogsFileBrowserOptions};
/// const EXTENSION: &CStr = cstr!("*");
/// // SAFETY: `EXTENSION` is a valid UTF-8 string
/// let mut options = unsafe { DialogFileBrowserOptions::new(EXTENSION) };
/// {
/// let base_path_bytes = [b'/', b'r', b'o', b'o', b't'];
/// let base_path = CStr::from_bytes_with_nul(&base_path_bytes).unwrap();
/// options = options.set_base_path(base_path);
/// }
/// ```
pub unsafe fn with_extension(extension: &'a CStr) -> Self {
let mut options = MaybeUninit::<sys::DialogsFileBrowserOptions>::uninit();
let uninit_options = options.as_mut_ptr();
let extension = extension.as_ptr();
// TODO: as for now, we stick to default (NULL) icon,
// although we may want to make it customizable via this function's parameter
// once there are safe Icon-related APIs
let icon = ptr::null();
// SAFETY: all pointers are valid (`icon` is allowed to be NULL)
// and options is intentionally uninitialized
// since it is the called function's job to do it
unsafe { sys::dialog_file_browser_set_basic_options(uninit_options, extension, icon) };
Self {
// default values from sys::dialog_file_browser_set_basic_options()
data: sys::DialogsFileBrowserOptions {
extension: c"*".as_ptr(),
base_path: ptr::null(),
skip_assets: true,
hide_dot_files: false,
icon: ptr::null(),
hide_ext: true,
item_loader_callback: None,
item_loader_context: ptr::null_mut(),
},
// SAFETY: data has just been initialized fully
// as guaranteed by the previously called function's contract
data: unsafe { options.assume_init() },
_phantom: PhantomData,
}
}

/// Set file extension to be offered for selection.
pub fn set_extension(
mut self,
// FIXME: this is unsound for non-UTF8 string
extension: &'a CStr,
) -> Self {
///
/// # Safety
///
/// `extension` should be a valid UTF-8 string
///
/// # Compatibility
///
/// This function's signature may change in the future to make it safe.
pub unsafe fn set_extension(mut self, extension: &'a CStr) -> Self {
self.data.extension = extension.as_ptr();
self
}

/// Set root folder path for navigation with back key.
pub fn set_base_path(
mut self,
// FIXME: this is unsound for non-UTF8 string
base_path: &'a CStr,
) -> Self {
///
/// # Safety
///
/// `base_path` should be a valid UTF-8 string
///
/// # Compatibility
///
/// This function's signature may change in the future to make it safe.
pub unsafe fn set_base_path(mut self, base_path: &'a CStr) -> Self {
self.data.base_path = base_path.as_ptr();
self
}
Expand Down

0 comments on commit 14ef1b9

Please sign in to comment.