fast-down-api 0.1.1

A convenient and easy-to-use FFI wrapper for fast-down
Documentation
use super::{PlanCommon, ResumeOutcome};
use crate::{Event, StateError, TerminationReason};
use std::io;
use tokio::fs;

#[derive(Clone, Copy)]
pub(super) enum MismatchPolicy {
    Fresh,
    Fail,
}

pub(super) struct MismatchPlan {
    common: PlanCommon,
    outcome: ResumeOutcome,
    policy: MismatchPolicy,
}

impl MismatchPlan {
    pub(super) const fn new(common: PlanCommon, error: StateError, policy: MismatchPolicy) -> Self {
        Self {
            common,
            outcome: ResumeOutcome::Mismatch(error),
            policy,
        }
    }

    pub(super) const fn common(&self) -> &PlanCommon {
        &self.common
    }

    pub(super) const fn outcome(&self) -> &ResumeOutcome {
        &self.outcome
    }

    pub(super) fn into_common(self) -> PlanCommon {
        self.common
    }

    pub(super) async fn run(self) -> TerminationReason {
        match self.policy {
            MismatchPolicy::Fresh => self.common.claim_and_run().await,
            MismatchPolicy::Fail => {
                let ResumeOutcome::Mismatch(error) = self.outcome else {
                    unreachable!("MismatchPlan must contain a mismatch outcome")
                };
                let _ = self.common.tx.send(Event::ResumeError(error));
                TerminationReason::Failed
            }
        }
    }

    pub(super) async fn run_forced(self) -> TerminationReason {
        let ResumeOutcome::Mismatch(planned_error) = self.outcome else {
            unreachable!("MismatchPlan must contain a mismatch outcome")
        };
        if !matches!(
            &planned_error,
            StateError::FileChanged {
                local_file_size,
                remote_file_size,
                ..
            } if local_file_size == remote_file_size
        ) {
            let _ = self.common.tx.send(Event::ResumeError(planned_error));
            return TerminationReason::Failed;
        }

        let state = match crate::DownloadState::load(&self.common.cfg_path).await {
            Ok(state) => state,
            Err(e) => {
                let _ = self.common.tx.send(Event::ResumeError(e));
                return TerminationReason::Failed;
            }
        };
        match state.validate(&self.common.info) {
            Ok(()) => {}
            Err(StateError::FileChanged {
                local_file_size,
                remote_file_size,
                ..
            }) if local_file_size == remote_file_size => {}
            Err(e) => {
                let _ = self.common.tx.send(Event::ResumeError(e));
                return TerminationReason::Failed;
            }
        }
        if !fs::try_exists(&self.common.tmp_path).await.unwrap_or(false) {
            let _ = self
                .common
                .tx
                .send(Event::ResumeError(StateError::Open(io::Error::new(
                    io::ErrorKind::NotFound,
                    "the .part file disappeared after the plan was created",
                ))));
            return TerminationReason::Failed;
        }
        state.merge_config(&self.common.partial_config);
        if let Some((actual_size, recorded_size)) =
            state.part_shortfall(&self.common.tmp_path).await
        {
            let _ = self
                .common
                .tx
                .send(Event::ResumeError(StateError::Truncated {
                    actual_size,
                    recorded_size,
                }));
            return TerminationReason::Failed;
        }
        state.refresh_identity(&self.common.url, &self.common.info);
        self.common.run_existing(state, true).await
    }
}