tina-core 0.0.2

Tina platform
Documentation
//! excel数据
use crate::tina::data::json::ToJson;
use crate::tina::data::AppResult;
use crate::{app_debug, app_system_error};
use crate::{app_error_from, tina::data::binary::StoredFileData};
use indexmap::IndexMap;
use mime::Mime;
use serde::Serialize;
use serde_json::Value;
use std::borrow::Cow;
use std::fs::File;
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::path::Path;
use std::sync::{Arc, Mutex};
use xlsxwriter::prelude::{FormatAlignment, FormatVerticalAlignment};
use xlsxwriter::{Format, Workbook, Worksheet};

use super::json::JsonCaseExt;

#[allow(dead_code)]
/// 本地Excel存储文件数据
pub struct ExcelFileData {
    /// 文件存储路径
    pub store_path: Arc<String>,
    read_handle: Option<File>,
    chunk_size: usize,
    delete_on_drop: bool,
    sheet_row_no: IndexMap<String, u32>,
    copy_lock: Arc<Mutex<()>>,
}

impl ExcelFileData {
    /// 构建文件数据
    pub fn new<P: AsRef<Path>>(path: P, delete_on_drop: bool) -> ExcelFileData {
        ExcelFileData {
            store_path: Arc::new(path.as_ref().to_string_lossy().to_string()),
            read_handle: None,
            chunk_size: 8192,
            delete_on_drop,
            sheet_row_no: Default::default(),
            copy_lock: Arc::new(Mutex::new(())),
        }
    }
    /// 获取content-type
    pub fn get_content_type(&self) -> Mime {
        crate::new_mime_guess::from_path(self.store_path.as_str()).first_or(mime::APPLICATION_OCTET_STREAM)
    }
    /// 获取文件名
    pub fn get_file_name(&self) -> Cow<str> {
        Path::new(self.store_path.as_str()).file_name().map(|v| v.to_string_lossy()).unwrap_or_default()
    }
    /// 创建写入
    pub fn create_write_handle(&self) -> AppResult<Workbook> {
        let path = Path::new(self.store_path.as_str());
        if let Some(parent) = path.parent() {
            if !parent.exists() {
                if let Err(err) = std::fs::create_dir_all(parent) {
                    return Err(app_system_error!("创建excel所在目录失败: {}, reason: {:?}", path.to_string_lossy(), err));
                }
            }
        }
        let func = AssertUnwindSafe(|| Workbook::new(self.store_path.as_str()));
        let r = catch_unwind(func);
        let workbook = r
            .map_err(|_| app_system_error!("创建excel失败: {}", self.store_path.as_str()))?
            .map_err(|_| app_system_error!("创建excel失败: {}", self.store_path.as_str()))?;
        Ok(workbook)
    }
    /// 添加格式
    fn add_format(&self, _handle: &Workbook) -> AppResult<Format> {
        let func = AssertUnwindSafe(Format::new);
        let format: Format = catch_unwind(func).map_err(|_| app_system_error!("excel添加format失败: {}", self.store_path.as_str()))?;
        Ok(format)
    }
    /// 生成header格式
    pub fn generate_header_format(&self, handle: &Workbook) -> AppResult<Format> {
        let mut format = self.add_format(handle)?;
        format.set_align(FormatAlignment::Center).set_vertical_align(FormatVerticalAlignment::VerticalCenter).set_bold();
        Ok(format)
    }
    /// 生成数据格式bool
    pub fn generate_bool_format(&self, handle: &Workbook) -> AppResult<Format> {
        let mut format = self.add_format(handle)?;
        format.set_align(FormatAlignment::Center).set_vertical_align(FormatVerticalAlignment::VerticalCenter);
        Ok(format)
    }
    /// 生成数据格式number
    pub fn generate_number_format(&self, handle: &Workbook) -> AppResult<Format> {
        let mut format = self.add_format(handle)?;
        format.set_align(FormatAlignment::Right).set_vertical_align(FormatVerticalAlignment::VerticalCenter);
        Ok(format)
    }
    /// 生成数据格式simple string
    pub fn generate_simple_string_format(&self, handle: &Workbook) -> AppResult<Format> {
        let mut format = self.add_format(handle)?;
        format.set_align(FormatAlignment::Left).set_vertical_align(FormatVerticalAlignment::VerticalCenter);
        Ok(format)
    }
    /// 生成数据格式simple string
    pub fn generate_rich_string_format(&self, handle: &Workbook) -> AppResult<Format> {
        let mut format = self.add_format(handle)?;
        format.set_text_wrap().set_align(FormatAlignment::Left).set_vertical_align(FormatVerticalAlignment::VerticalCenter);
        Ok(format)
    }
    /// 添加表头
    pub fn add_header<S: Serialize>(
        &mut self,
        handle: &Workbook,
        sheet_name: &String,
        header: &S,
        bool_format: Option<&Format>,
        number_format: Option<&Format>,
        string_format: Option<&Format>,
    ) -> AppResult<()> {
        let mut sheet = self.get_sheet(handle, sheet_name)?;
        let value = header.to_json_value();
        let row = self.get_sheet_row_no(sheet_name);
        Self::write_value(&mut sheet, row, 0, &value, bool_format, number_format, string_format)?;
        self.set_sheet_row_no(sheet_name, row + 1);
        Ok(())
    }
    /// 添加数据
    pub fn add_data<S: Serialize>(
        &mut self,
        handle: &Workbook,
        sheet_name: &String,
        data: &S,
        bool_format: Option<&Format>,
        number_format: Option<&Format>,
        string_format: Option<&Format>,
    ) -> AppResult<()> {
        let mut sheet = self.get_sheet(handle, sheet_name)?;
        let value = data.to_json_value();
        let row = self.get_sheet_row_no(sheet_name);
        Self::write_value(&mut sheet, row, 0, &value, bool_format, number_format, string_format)?;
        self.set_sheet_row_no(sheet_name, row + 1);
        Ok(())
    }
    /// 添加数据
    pub fn add_value(
        &mut self,
        handle: &Workbook,
        sheet_name: &String,
        value: Value,
        bool_format: Option<&Format>,
        number_format: Option<&Format>,
        string_format: Option<&Format>,
    ) -> AppResult<()> {
        let mut sheet = self.get_sheet(handle, sheet_name)?;
        let row = self.get_sheet_row_no(sheet_name);
        Self::write_value(&mut sheet, row, 0, &value, bool_format, number_format, string_format)?;
        self.set_sheet_row_no(sheet_name, row + 1);
        Ok(())
    }

    fn write_value(
        sheet: &mut Worksheet,
        row: u32,
        mut col: u16,
        value: &Value,
        bool_format: Option<&Format>,
        number_format: Option<&Format>,
        string_format: Option<&Format>,
    ) -> AppResult<()> {
        match value {
            Value::Null => {}
            Value::Bool(v) => {
                sheet
                    .write_boolean(row, col, *v, bool_format)
                    .map_err(|err| app_system_error!("write failed: {}, reason: {:?}", v, err))?;
            }
            Value::Number(_) => {
                let v = value.try_as_f64()?;
                sheet
                    .write_number(row, col, v, number_format)
                    .map_err(|err| app_system_error!("write failed: {}, reason: {:?}", v, err))?;
            }
            Value::String(v) => {
                sheet
                    .write_string(row, col, v.as_str(), string_format)
                    .map_err(|err| app_system_error!("write failed: {}, reason: {:?}", v, err))?;
            }
            Value::Array(v) => {
                for item in v.iter() {
                    Self::write_value(sheet, row, col, item, bool_format, number_format, string_format)?;
                    col += 1;
                }
            }
            Value::Object(v) => {
                for (_, item) in v.iter() {
                    Self::write_value(sheet, row, col, item, bool_format, number_format, string_format)?;
                    col += 1;
                }
            }
        }
        Ok(())
    }

    fn get_sheet<'a>(&self, handle: &'a Workbook, sheet_name: &String) -> AppResult<Worksheet<'a>> {
        match handle.get_worksheet(sheet_name.as_str()).map_err(app_error_from!())? {
            None => {
                let worksheet1 = handle
                    .add_worksheet(Some(sheet_name))
                    .map_err(|err| app_system_error!("add sheet failed: {}, reason: {:?}", sheet_name, err))?;
                Ok(worksheet1)
            }
            Some(v) => Ok(v),
        }
    }
    fn get_sheet_row_no(&self, sheet_name: &String) -> u32 {
        self.sheet_row_no.get(sheet_name).copied().unwrap_or_default()
    }
    fn set_sheet_row_no(&mut self, sheet_name: &String, row_no: u32) {
        self.sheet_row_no.insert(sheet_name.to_string(), row_no);
    }
    /// 刷新文件内容至磁盘
    pub fn flush_and_close(&self, handle: Workbook) -> AppResult<()> {
        handle.close().map_err(|v| app_system_error!("刷新文件内容至磁盘失败: {}, reason: {:?}", self.store_path.as_str(), v))?;
        Ok(())
    }
    /// 转换
    pub fn into_stored_file_data(mut self) -> StoredFileData {
        let data = StoredFileData::new(self.store_path.as_str(), self.delete_on_drop);
        self.delete_on_drop = false;
        data
    }
}

impl Drop for ExcelFileData {
    fn drop(&mut self) {
        if !self.delete_on_drop {
            return;
        }
        let _lock = self.copy_lock.lock();
        if Arc::strong_count(&self.store_path) <= 1 {
            match std::fs::remove_file(self.store_path.as_str()) {
                Ok(_) => {
                    app_debug!("删除文件: {}", self.store_path)
                }
                Err(err) => {
                    tracing::error!("删除文件失败: {}, reason: {:?}", self.store_path.as_str(), err);
                }
            }
        }
    }
}