tessera-mobile 0.0.0

Rust on mobile made easy.
Documentation
use std::path::{Path, PathBuf};

use thiserror::Error;

use crate::{
    os,
    target::TargetTrait as _,
    util::{
        cli::{Report, Reportable},
        ln, prefix_path,
    },
};

use super::{config::Config, target::Target};

#[derive(Debug, Error)]
pub enum RemoveBrokenLinksError {
    #[error("Failed to list contents of jniLibs directory {dir}: {source}")]
    ReadDir {
        dir: PathBuf,
        source: std::io::Error,
    },
    #[error("Failed to get entry in jniLibs directory {dir}: {source}")]
    Entry {
        dir: PathBuf,
        source: std::io::Error,
    },
    #[error("Failed to remove broken symlink {path}: {source}")]
    Remove {
        path: PathBuf,
        source: std::io::Error,
    },
}

impl Reportable for RemoveBrokenLinksError {
    fn report(&self) -> Report {
        match self {
            Self::ReadDir { dir, source } => Report::error(
                format!("Failed to list contents of jniLibs directory {dir:?}"),
                source,
            ),
            Self::Entry { dir, source } => Report::error(
                format!("Failed to get entry in jniLibs directory {dir:?}"),
                source,
            ),
            Self::Remove { path, source } => {
                Report::error(format!("Failed to remove broken symlink {path:?}"), source)
            }
        }
    }
}

#[derive(Debug, Error)]
pub enum SymlinkLibError {
    #[error("The symlink source is {0}, but nothing exists there")]
    SourceMissing(PathBuf),
    #[error(transparent)]
    SymlinkFailed(ln::Error),
}

impl Reportable for SymlinkLibError {
    fn report(&self) -> Report {
        Report::error("Failed to symlink lib", self)
    }
}

pub fn path(config: &Config, target: Target<'_>) -> PathBuf {
    prefix_path(
        config.project_dir(),
        format!("app/src/main/jniLibs/{}", &target.abi),
    )
}

#[derive(Debug)]
pub struct JniLibs {
    path: PathBuf,
}

impl JniLibs {
    pub fn create(config: &Config, target: Target<'_>) -> std::io::Result<Self> {
        let path = path(config, target);
        std::fs::create_dir_all(&path).map(|()| Self { path })
    }

    pub fn remove_broken_links(config: &Config) -> Result<(), RemoveBrokenLinksError> {
        for abi_dir in Target::all()
            .values()
            .map(|target| path(config, *target))
            .filter(|path| path.is_dir())
        {
            for entry in
                std::fs::read_dir(&abi_dir).map_err(|source| RemoveBrokenLinksError::ReadDir {
                    dir: abi_dir.clone(),
                    source,
                })?
            {
                let entry = entry
                    .map_err(|source| RemoveBrokenLinksError::Entry {
                        dir: abi_dir.clone(),
                        source,
                    })?
                    .path();
                if let Ok(path) = std::fs::read_link(&entry) {
                    log::info!("symlink at {:?} points to {:?}", entry, path);
                    if !path.exists() {
                        log::info!(
                            "deleting broken symlink {:?} (points to {:?}, which doesn't exist)",
                            entry,
                            path
                        );
                        std::fs::remove_file(entry)
                            .map_err(|source| RemoveBrokenLinksError::Remove { path, source })?;
                    }
                }
            }
        }
        Ok(())
    }

    pub fn symlink_lib(&self, src: &Path) -> Result<(), SymlinkLibError> {
        log::info!("symlinking lib {:?} in jniLibs dir {:?}", src, self.path);
        if src.is_file() {
            let dest = self.path.join(
                src.file_name()
                    .expect("developer error: file had no file name"),
            );
            os::ln::force_symlink(src, dest, ln::TargetStyle::File)
                .map_err(SymlinkLibError::SymlinkFailed)?;
            Ok(())
        } else {
            Err(SymlinkLibError::SourceMissing(src.to_owned()))
        }
    }
}