Viewing: builder.rs

// SPDX-License-Identifier: MIT

// Copyright (c) 2025 DDN. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

use std::ffi::CString;

use super::{
    error::{ChangelogError, Result as ChangelogResult},
    flags::{ChangelogExtraFlag, ChangelogFlag},
    reader::ChangelogReader,
};
use crate::error::cvt_nz;
use changelog_sys::*;

/// Builder for configuring and connecting to a Lustre changelog.
///
/// This builder provides a fluent interface for configuring changelog parameters
/// before establishing a connection.
///
/// # Example
/// ```rust,no_run
/// use rustreapi::changelog::{ChangelogBuilder, ChangelogFlag, ChangelogExtraFlag};
///
/// let reader = ChangelogBuilder::new()
///     .device("lustre-MDT0000")
///     .flags(ChangelogFlag::Follow | ChangelogFlag::Block)
///     .extra_flags(ChangelogExtraFlag::UidGid | ChangelogExtraFlag::Nid)
///     .start_record(0)
///     .connect()?;
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[derive(Debug, Clone)]
pub struct ChangelogBuilder {
    device: Option<String>,
    flags: ChangelogFlag,
    extra_flags: Option<ChangelogExtraFlag>,
    start_record: i64,
}

impl ChangelogBuilder {
    /// Create a new changelog builder with default settings.
    pub fn new() -> Self {
        Self {
            device: None,
            flags: ChangelogFlag::none(),
            extra_flags: None,
            start_record: 0,
        }
    }

    /// Set the MDT device name to connect to.
    ///
    /// # Example
    /// ```rust,no_run
    /// # use rustreapi::changelog::ChangelogBuilder;
    /// let builder = ChangelogBuilder::new().device("lustre-MDT0000");
    /// ```
    pub fn device<S: Into<String>>(mut self, device: S) -> Self {
        self.device = Some(device.into());
        self
    }

    /// Set the changelog flags.
    ///
    /// # Example
    /// ```rust,no_run
    /// # use rustreapi::changelog::{ChangelogBuilder, ChangelogFlag};
    /// let builder = ChangelogBuilder::new()
    ///     .flags(ChangelogFlag::Follow | ChangelogFlag::Block);
    /// ```
    pub fn flags(mut self, flags: ChangelogFlag) -> Self {
        self.flags = flags;
        self
    }

    /// Set the extra flags for requesting additional extension data.
    ///
    /// # Example
    /// ```rust,no_run
    /// # use rustreapi::changelog::{ChangelogBuilder, ChangelogExtraFlag};
    /// let builder = ChangelogBuilder::new()
    ///     .extra_flags(ChangelogExtraFlag::UidGid | ChangelogExtraFlag::Nid);
    /// ```
    pub fn extra_flags(mut self, extra_flags: ChangelogExtraFlag) -> Self {
        self.extra_flags = Some(extra_flags);
        self
    }

    /// Set the starting record index.
    ///
    /// Records with indices less than this value will be skipped.
    ///
    /// # Example
    /// ```rust,no_run
    /// # use rustreapi::changelog::ChangelogBuilder;
    /// let builder = ChangelogBuilder::new().start_record(12345);
    /// ```
    pub fn start_record(mut self, start_record: i64) -> Self {
        self.start_record = start_record;
        self
    }

    /// Establish a connection to the changelog and return a reader.
    ///
    /// This method will call `llapi_changelog_start` followed by
    /// `llapi_changelog_set_xflags` if extra flags are configured.
    ///
    /// # Errors
    /// Returns an error if:
    /// - No device was specified
    /// - The changelog connection could not be established
    /// - Setting extra flags failed
    pub fn connect(self) -> ChangelogResult<ChangelogReader> {
        let device = self.device.ok_or(ChangelogError::MissingDevice)?;

        let device_cstr =
            CString::new(device.clone()).map_err(|e| ChangelogError::InvalidDevice {
                device: device.clone(),
                source: e,
            })?;

        let mut priv_ptr: *mut std::os::raw::c_void = std::ptr::null_mut();

        // Start the changelog connection
        unsafe {
            cvt_nz(llapi_changelog_start(
                &mut priv_ptr,
                self.flags.bits(),
                device_cstr.as_ptr(),
                self.start_record as std::os::raw::c_longlong,
            ))
            .map_err(|e| ChangelogError::StartFailed {
                device: device.clone(),
                source: Box::new(e),
            })?;
        }

        if priv_ptr.is_null() {
            return Err(ChangelogError::StartInvalidResult {
                device: device.clone(),
            });
        }

        // Set extra flags if specified
        if let Some(extra_flags) = self.extra_flags {
            unsafe {
                cvt_nz(llapi_changelog_set_xflags(priv_ptr, extra_flags.bits())).map_err(|e| {
                    // Clean up the connection on error
                    let mut cleanup_ptr = priv_ptr;
                    let _ = llapi_changelog_fini(&mut cleanup_ptr);
                    ChangelogError::SetExtraFlagsFailed {
                        device: device.clone(),
                        source: Box::new(e),
                    }
                })?;
            }
        }

        Ok(ChangelogReader::new(priv_ptr, device, self.flags))
    }
}

impl Default for ChangelogBuilder {
    fn default() -> Self {
        Self::new()
    }
}